<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <!-- 可修改 Editable - 默认不屏蔽搜索引擎机器人，想屏蔽的请去除注释。 Default allow search engine robots. -->
    <!-- <meta name="ROBOTS" content="noindex,nofllow"> -->
    <!-- 可修改 Editable - 网页标题 -->
    <title>Blog - Chat0.1</title>
    <meta name="description" content="LinGPT - A GPT-3.5 Webpage with Just a Single HTML File."/>
    <!-- 网页在手机/平板上的显示比例  默认缩放80%（ initial-scale=0.8 ） -->
    <meta name="viewport"
          content="width=device-width,initial-scale=0.8,minimum-scale=0.5,maximum-scale=2,user-scalable=yes,viewport-fit=cover">
    <link rel="shortcut icon" href="/img/favicon.ico">
    <!-- 可修改 Editable - iPhone/iPad将网页添加到主屏幕后显示的Logo  默认为ChatGPT的Logo  *小白不需要改。需要传网页到Github/Gitee/静态网页托管平台/主机/服务器的同学，才需要设置。  简单点快速设置：任意格式的正方形图片。  推荐参考设置：png格式 180x180  -->
    <link rel="apple-touch-icon" href="/img/favicon.ico">
    <!--操作 cookie -->
    <th:block th:include="include :: js-cookie"/>

    <!-- Vue首选CDN(线路1) Vue是一款用于构建用户界面的JavaScript框架，网页常用技术  -->
    <!-- 20230520 tips: 不要使用3.2.47的prod版本，会偶尔产生错误，影响使用。 Do not use the prod version of 3.2.47, as I have found it to occasionally produce errors with unknown causes. -->
    <!-- 20230603 tips: 使用3.3.4的prod版本两周了，非常稳定，没有再报错，很棒的版本。  I've been using the prod version of 3.3.4 for two weeks now and it has been very stable, without any errors. It's a great version.  -->
<!--    <script src="https://unpkg.com/vue@3.3.4/dist/vue.global.prod.js"></script>-->
    <script th:src="@{/blog/mblog/js/vue.global.prod.js}"></script>

    <!-- Vue备选CDN(线路2) 首选CDN失效后，自动加载备选CDN，确保vue的正确加载。 unescape()括号里的是一种编码，转义尖括号(<>)，非乱码。在线解码工具 https://tool.ip138.com/escape/   -->
    <script> !window.Vue && document.write(unescape('%3Cscript src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"%3E%3C/script%3E')) </script>


    <!-- 以下为css样式 已压缩，可自行使用工具格式化展开  *压缩后位于末尾的属性会省略掉分号';' -->
    <!-- CSS格式化工具1: https://tool.ip138.com/css/ -->
    <!-- CSS格式化工具2: http://www.ab173.com/gongju/format/css.php  -->
    <style scope>

        #loading-mask {
            background-color: #ededed;
            z-index: 900999
        }

        #loading-mask div {
            text-align: center;
            font-size: 12px;
            color: #b4b4b4
        }

        ::-webkit-scrollbar {
            width: 7px;
            height: 7px
        }

        ::-webkit-scrollbar-thumb {
            background-color: #e1e1e1;
            border-radius: 10px
        }

        ::-webkit-scrollbar-thumb:hover {
            background-color: #cdcdcd
        }

        body::-webkit-scrollbar-thumb {
            background-color: #e1e1e1;
            border-radius: 10px
        }

        body::-webkit-scrollbar-thumb:hover {
            background-color: #cdcdcd
        }

        body::-webkit-scrollbar-track {
            background-color: #ededed
        }

        body::-webkit-scrollbar-track:hover {
            background-color: #ededed
        }

        body::-webkit-scrollbar-corner {
            background-color: #ededed
        }

        .dialog-wrapper .dialog-scroll::-webkit-scrollbar-thumb {
            background-color: #d2d2d2;
            border-radius: 10px
        }

        .dialog-wrapper .dialog-scroll::-webkit-scrollbar-thumb:hover {
            background-color: #c3c3c3
        }

        .dialog-wrapper .dialog-scroll::-webkit-scrollbar-track {
            background-color: #ededed;
            border-radius: 10px
        }

        .dialog-wrapper .dialog-scroll::-webkit-scrollbar-track:hover {
            background-color: #ededed
        }

        .dialog-wrapper .dialog-scroll::-webkit-scrollbar-corner {
            background-color: #ededed
        }

        .notification-wrapper .dialog-scroll::-webkit-scrollbar-thumb {
            background-color: #e8eb95;
            border-radius: 10px
        }

        .notification-wrapper .dialog-scroll::-webkit-scrollbar-thumb:hover {
            background-color: #dadd8b
        }

        .notification-wrapper .dialog-scroll::-webkit-scrollbar-track {
            background-color: #fdffb4
        }

        .notification-wrapper .dialog-scroll::-webkit-scrollbar-track:hover {
            background-color: #fdffb4
        }

        .notification-wrapper .dialog-scroll::-webkit-scrollbar-corner {
            background-color: #fdffb4
        }

        body {
            margin-bottom: 0px;
            margin-top: 8px;
            overflow-y: scroll;
            background-color: #ededed;
            font-family: Helvetica Neue, Helvetica, Hiragino Sans GB, "SF Pro SC", Microsoft YaHei, "PingFang SC", Arial, sans-serif
        }

        .bottom-position {
            height: 95px
        }

        .newcontext-hr {
            height: 1px;
            background-color: #cfcfcf;
            border: none;
            width: 92%;
            margin-top: 16px;
            margin-bottom: 20px
        }

        .wechatstyle-datetime {
            display: block;
            font-size: 13px;
            text-align: center;
            color: #bbbbbb;
            margin-top: 20px;
            margin-bottom: 17px;
            -webkit-text-size-adjust: 100%;
        }

        .msgdatetimediv {
            position: relative;
            -webkit-text-size-adjust: 100%;
        }

        .usermsg-datetime {
            font-size: 12px;
            color: #bbbbbb;
            bottom: auto;
            left: auto;
            right: 54px;
            top: -14px;
            position: absolute
        }

        .aimsg-datetime {
            font-size: 12px;
            color: #bbbbbb;
            bottom: auto;
            left: 57px;
            right: auto;
            position: absolute
        }

        .userinfo {
            display: flex;
            flex-direction: row-reverse;
            align-items: flex-start;
            padding-right: 2px;
            margin-top: 17px;
            animation: oneshow 0.8s ease 1
        }

        .usermsg {
            color: #191919;
            display: flex;
            flex-direction: column;
            justify-content: center;
            padding: 9px 13px;
            border-radius: 6px;
            overflow-x: auto;
            overflow-y: hidden;
            scrollbar-width: thin;
            margin-right: 11px;
            background-color: #95eb6c
        }

        .aiinfo {
            display: flex;
            flex-direction: row;
            align-items: flex-start;
            margin-left: 7px;
            margin-top: 17px;
            animation: oneshow 0.8s ease 1
        }

        .aimsg {
            margin-top: 20px;
            color: #191919;
            display: flex;
            flex-direction: column;
            justify-content: center;
            padding: 9px 13px;
            border-radius: 6px;
            overflow-x: auto;
            overflow-y: hidden;
            scrollbar-width: thin;
            margin-left: 11px;
            background-color: #ffffff
        }

        .aiinfo .msgdiv::before {
            margin-top: 11px;
            content: "";
            display: block;
            position: absolute;
            border: 12px solid transparent;
            border-right-color: rgba(255, 255, 255, 1);
            left: -7px;
            top: 8px
        }

        .userinfo .msgdiv::before {
            content: "";
            display: block;
            position: absolute;
            border: 12px solid transparent;
            border-left-color: #95eb6c;
            right: -7px;
            top: 8px
        }

        .msgcopydiv {
            width: auto;
            max-width: 76%
        }

        .chat-img {
            border-radius: 6px;
            height: 2.3rem;
            width: 2.3rem;
            color: rgba(255, 255, 255);
            background-color: #f5f5f5;
            display: flex;
            flex-direction: row;
            justify-content: center;
            align-items: center
        }

        .chat-img img {
            height: 100%;
            width: 100%;
            object-fit: cover;
            border-radius: 6px
        }

        .chat-green-svg {
            background: linear-gradient(to bottom, #17c882, #14b482);
        }

        .chat-img-border {
            box-shadow: 0 0 2px 1px rgba(56, 82, 56, 0.3);
            -webkit-box-shadow: 0 0 2px 1px rgba(56, 82, 56, 0.3);
            -moz-box-shadow: 0 0 2px 1px rgba(56, 82, 56, 0.3)
        }

        .chat-img-bg {
            background-image: radial-gradient(#fff, rgb(245, 245, 245) 40%, #e6e6e6);
            background-image: -webkit-radial-gradient(#fff, rgb(245, 245, 245) 40%, #e6e6e6);
            background-image: -moz-radial-gradient(#fff, rgb(245, 245, 245) 40%, #e6e6e6)
        }

        .update-img-size {
            margin-right: 4px !important;
            height: 33px;
            width: 33px
        }

        .flex-column-center {
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: flex-start;
            position: fixed;
            bottom: 0px;
            width: 100%;
            left: 0px;
            background-color: #f6f6f6;
            border-top: 0.8px solid #dddddd
        }

        .justify-end {
            display: flex;
            flex-direction: row;
            justify-content: flex-start;
            align-items: right;
            bottom: 0px
        }

        .panelrow {
            display: flex;
            flex-direction: row;
            justify-content: space-between;
            align-items: flex-end;
            width: 100%;
            height: 30px
        }

        .dh-input {
            font-size: 14px;
            color: #191919;
            width: 100%;
            height: 25px;
            border-radius: 6px;
            padding-left: 10px;
            padding-right: 10px;
            margin-left: 10px;
            margin-right: 4px;
            border: 1px solid #DDD;
            -webkit-box-sizing: border-box;
            -moz-box-sizing: border-box;
            box-sizing: border-box;
            scrollbar-width: thin;
        }

        .dh-input:disabled {
            border: 1px solid #DDD;
            color: #cccccc !important
        }

        .dh-input:read-only {
            border: 1px solid #DDD;
            color: #cccccc !important
        }

        .textareaSPReadOnly:read-only {
            color: #555555 !important;
            background-color: #ededed !important;
            overflow: scroll
        }

        input[type=text], input[type=password], textarea {
            outline: none
        }

        input[type=text]:focus, input[type=password]:focus, textarea:focus {
            border: 1px solid #17a316 !important;
            box-shadow: inset 0 1px 2px rgba(0, 0, 0, .075), 0 0 5px rgba(81, 167, 232, .5)
        }

        input:focus {
            z-index: 2
        }

        .dropdown {
            -webkit-appearance: none;
            -moz-appearance: none;
            appearance: none;
            flex-shrink: 0;
            width: auto;
            height: 25px;
            align-items: center;
            justify-content: center;
            margin: 0;
            padding: 0 10px;
            border-radius: 6px;
            border: none;
            font-weight: 400;
            font-size: 12px;
            overflow: hidden;
            outline: none;
            cursor: pointer;
            color: #FFF;
            background: #1aad19;
            background: linear-gradient(to bottom, #1ac83c, #1aa819);
            -webkit-box-sizing: border-box;
            -moz-box-sizing: border-box;
            box-sizing: border-box
        }

        .btn {
            display: flex;
            flex-shrink: 0;
            width: auto;
            height: 25px;
            align-items: center;
            justify-content: center;
            padding: 0 10px;
            margin-right: 7px;
            background-color: #1aad19;
            border-color: #1aad19;
            background: linear-gradient(to bottom, #1ac83c, #1aa819);
            color: #FFF;
            border-radius: 6px;
            border: none;
            outline: none;
            font-weight: 400;
            font-size: 12px;
            text-decoration: none;
            text-align: center;
            line-height: 25px;
            cursor: pointer;
            -webkit-box-sizing: border-box;
            -moz-box-sizing: border-box;
            box-sizing: border-box;
            -webkit-transition-property: all;
            transition-property: all;
            -webkit-transition-duration: .3s;
            transition-duration: .3s
        }

        .btn *, .copybtn * {
            align-items: center;
            justify-content: center;
            display: flex
        }

        .btn:visited, .dropdown:visited {
            color: #FFF
        }

        .btn:hover, .btn:focus, .dropdown:hover {
            background-color: #25c524;
            border-color: #25c524;
            background: linear-gradient(to bottom, #24dc46, #24b423);
            color: #FFF
        }

        .dropdown:active, .dropdown:focus {
            background: #0fb9af;
            background-color: #0fb9af;
            border-color: #0fb9af;
            color: #f5feff
        }

        .dropdown option:disabled {
            color: #0c8b6e;
        }

        .bluedropdown option:disabled {
            color: #008398;
        }

        .orangereddropdown option:disabled {
            color: #a23300;
        }

        .btn:active {
            background-color: #17a316;
            border-color: #17a316;
            background: linear-gradient(to bottom, #17be39, #179616);
            color: #f9fff5;
        }

        .btn:disabled, .dropdown:disabled, .bluebtn:disabled {
            border: 1px solid #DDD !important;
            background-color: #F5F5F5 !important;
            background: linear-gradient(to bottom, #fcfcfc, #f0f0f0) !important;
            color: #1aad19 !important
        }

        .minibtn {
            width: 14px;
            height: 14px;
            line-height: 12px;
            padding: 1px;
            font-size: 12px;
            flex-shrink: 0;
            display: inline-block;
            vertical-align: middle;
            border-radius: 50%;
            margin-bottom: 1px !important;
            transform: scale(0.85);
            -ms-transform: scale(0.85);
            -webkit-transform: scale(0.85);
            -moz-transform: scale(0.85);
            -o-transform: scale(0.85)
        }

        .minibtn-default {
            width: auto;
            height: 16px;
            padding: 2px 7px;
            border-radius: 4px
        }

        .bluebtn, .bluedropdown {
            background-color: #1ca5c2;
            border-color: #1ca5c2;
            background: linear-gradient(to bottom, #1cbec8, #1c9bc3);
        }

        .bluebtn:hover, .bluebtn:focus {
            background-color: #22b9d9;
            border-color: #22b9d9;
            background: linear-gradient(to bottom, #26d2d2, #26afcd);
            color: #FFF
        }

        .bluebtn:active {
            background-color: #1ca5c2;
            border-color: #1ca5c2;
            background: linear-gradient(to bottom, #19b4c5, #1991c0);
            color: #effcff
        }

        .bluedropdown:hover, .bluedropdown:focus {
            background-color: #22b9d9;
            border-color: #22b9d9;
            background: #22b9d9;
            color: #FFF
        }

        .redbtn {
            background-color: #dd3838;
            border-color: #dd3838;
            background: linear-gradient(to bottom, #f34d2d, #eb0505);
            box-shadow: 0 0 3px 0 rgba(221, 56, 56, 0.55)
        }

        .redbtn:hover, .redbtn:focus {
            background-color: #ff4040;
            border-color: #ff4040;
            background: linear-gradient(to bottom, #fd5f4b, #eb1919);
            color: #FFF
        }

        .redbtn:active {
            background-color: #c73737;
            border-color: #c73737;
            background: linear-gradient(to bottom, #ea4607, #ea0002);
            color: #fff5f0
        }

        .orangeredbtn, .orangereddropdown {
            background-color: #f44400;
            border-color: #f44400;
            background: linear-gradient(to bottom, #ff7800, #f74900);
            box-shadow: 0 0 2px 0 rgba(241, 64, 14, 0.55)
        }

        .orangeredbtn:hover, .orangeredbtn:focus {
            background-color: #ff4f00;
            border-color: #ff4f00;
            background: linear-gradient(to bottom, #ff8c0a, #fe580a);
            color: #FFF
        }

        .orangeredbtn:active {
            background-color: #da3600;
            border-color: #da3600;
            background: linear-gradient(to bottom, #fc6e00, #f13a00);
            color: #fff9f6
        }

        .orangereddropdown:hover, .orangereddropdown:focus {
            background-color: #ff4f00;
            border-color: #ff4f00;
            background: #ff4f00;
            color: #FFF
        }

        .btnzoom-a {
            transition: all 0.5s;
            cursor: pointer
        }

        .btnzoom-a:hover {
            transform: scale(1.18)
        }

        .btnzoom-b {
            transition: all 0.2s
        }

        .btnzoom-b:hover {
            transform: scale(1)
        }

        .btnzoom-c {
            transition: all 0.2s
        }

        .btnzoom-c:hover {
            transform: scale(1.1)
        }

        .btnzoom-d {
            transition: all 0.2s
        }

        .btnzoom-d:hover {
            transform: scale(0.95)
        }

        .usermsg *, .aimsg * {
            margin: 0;
            word-wrap: break-word
        }

        /* white-space:normal去除上下空行（高度）（ blockquote 单独写）+ 列表后排除换行的可能  */
        .usermsg ul, .usermsg ol, .aimsg ul, .aimsg ol {
            white-space: normal;
            margin: 5px 0;
            padding-left: 28px
        }

        .usermsg p, .usermsg span, .aimsg p, .aimsg span {
            line-height: 1.6;
            margin-top: 3px
        }

        /* li: break-all 截断 */
        .usermsg li, .aimsg li {
            word-break: break-all
        }

        /* code 字体 */
        .usermsg code, .aimsg code {
            font-size: 12px;
            font-family: Consolas, Monaco, monospace
        }

        /* 行内代码 除了pre>code之外的代码块 */
        .aimsg code:not(pre code) {
            color: #27282c;
            white-space: pre-wrap;
            word-break: break-word;
            margin: 0;
            border-radius: 6px;
            padding: 1px 5px;
            background-color: #efefef
        }

        .usermsg code:not(pre code) {
            color: #27282c;
            white-space: pre-wrap;
            word-break: break-word;
            margin: 0;
            border-radius: 6px;
            padding: 1px 5px;
            background-color: #a9f289
        }

        /* 表格 table code */
        .usermsg table code:not(pre code), .aimsg table code:not(pre code) {
            color: #27282c;
            white-space: pre-wrap;
            word-break: break-word;
            margin: 0;
            border-radius: 6px;
            padding: 1px 5px;
            background-color: #e3e3d4
        }

        /* 代码块 */
        /* tokyo-night-dark color:#9aa5ce ; */
        /* tokyo-night-dark background: #1a1b26; */
        /* panda-syntax-dark color:#e6e6e6 ; */
        /* panda-syntax-dark background: #2a2c2d; */
        .usermsg pre, .aimsg pre {
            margin: 5px 0;
            width: 100%;
            min-width: 100px;
            white-space: pre;
            border-radius: 6px;
            box-sizing: border-box;
            color: #e6e6e6;
            background: #2a2c2d
        }

        .usermsg pre > code, .aimsg pre > code {
            display: block;
            padding: 1em;
            -moz-tab-size: 4;
            tab-size: 4;
            overflow-x: auto;
            white-space: pre;
            word-break: normal;
            scrollbar-width: thin
        }

        .usermsg img:not(.clippy), .aimsg img:not(.clippy) {
            margin: 5px 0;
            max-width: 100%
        }

        .usermsg table img, .aimsg table img {
            max-width: 300px
        }

        /* white-space:normal 去除上下空行（高度）*/
        .usermsg blockquote {
            white-space: normal;
            margin: 8px 14px;
            max-width: 100%;
            color: #373737;
            border-left: 1px solid #6eac4e;
            padding: 0 0 0 8px
        }

        .aimsg blockquote {
            white-space: normal;
            margin: 8px 14px;
            max-width: 100%;
            color: #57606a;
            border-left: 1px solid #c8c8c8;
            padding: 0 0 0 8px
        }

        .usermsg blockquote *, .aimsg blockquote * {
            margin-top: 0px;
            margin-bottom: 0px
        }

        .usermsg h1, .usermsg h2, .usermsg h3, .usermsg h4, .usermsg h5, .usermsg h6, .usermsg ul, .usermsg ol, .usermsg blockquote, .usermsg ul p, .usermsg ol p, .usermsg blockquote p, .aimsg h1, .aimsg h2, .aimsg h3, .aimsg h4, .aimsg h5, .aimsg h6, .aimsg ul, .aimsg ol, .aimsg blockquote, .aimsg ul p, .aimsg ol p, .aimsg blockquote p {
            line-height: 1.5
        }

        .usermsg h1, .usermsg h2, .usermsg h3, .usermsg h4, .usermsg h5, .usermsg h6, .aimsg h1, .aimsg h2, .aimsg h3, .aimsg h4, .aimsg h5, .aimsg h6 {
            margin-top: 5px
        }

        .usermsg h1, .aimsg h1 {
            font-size: 1.4em;
            margin: 0 0 0.5em 0;
            padding: 1em 0 0.5em;
            border-bottom: 1px solid #7dc35a
        }

        .usermsg h2, .aimsg h2 {
            font-size: 1.25em;
            margin: 0.5em 0;
            padding: 0.2em 0 0.3em;
            border-bottom: 1px solid #7dc35a
        }

        .aimsg h1 {
            border-bottom: 1px solid #b4b4b4
        }

        .aimsg h2 {
            border-bottom: 1px solid #b4b4b4
        }

        .usermsg h3, .aimsg h3 {
            font-size: 1.125em
        }

        .usermsg h4, .aimsg h4 {
            font-size: 1em
        }

        .usermsg h5, .aimsg h5 {
            font-size: .875em
        }

        .usermsg h6 {
            font-size: .85em;
            color: #373737
        }

        .aimsg h6 {
            font-size: .85em;
            color: #57606a
        }

        .usermsg hr {
            height: 1px;
            border: none;
            background-color: #7dc35a;
            margin: 0.6em 0 0.7em 0
        }

        .aimsg hr {
            height: 1px;
            border: none;
            background-color: #b4b4b4;
            margin: 0.6em 0 0.7em 0
        }

        .usermsg table, .aimsg table {
            margin: 5px 0;
            border-spacing: 0;
            border-collapse: collapse;
            display: table;
            max-width: 100%;
            overflow: auto;
            color: rgb(40, 40, 40);
            word-break: break-all
        }

        .usermsg td, .usermsg th, .aimsg td, .aimsg th {
            padding: 0
        }

        .usermsg table th, .aimsg table th {
            font-weight: 600
        }

        .usermsg table th, .usermsg table td, .aimsg table th, .aimsg table td {
            padding: 5px 8px;
            border: 1px solid #e4e6d0
        }

        .usermsg table tr, .aimsg table tr {
            background-color: #f6f9f1;
            border-top: 1px solid
        }

        .usermsg table tr:nth-child(2n), .aimsg table tr:nth-child(2n) {
            background-color: #edf1e5
        }

        .usermsg table img, .aimsg table img {
            background-color: transparent
        }

        /* 以下 一键复制相关 pre=snippet, button=copybtn ,svg/img(svg)=clippy */
        .clippy {
            margin: 4px;
            position: relative
        }

        .copybtn[disabled] .clippy {
            opacity: .3
        }

        .snippet, .msgcopydiv {
            position: relative;
            overflow: visible
        }

        .snippet .copybtn, .msgcopydiv .copybtn, .msgcopydiv .copybtn, .panelrow .copybtn, .notification-content .copybtn {
            -webkit-transition: opacity .3s ease-in-out;
            -o-transition: opacity .3s ease-in-out;
            transition: opacity .3s ease-in-out;
            opacity: 0;
            padding: 0;
            position: absolute;
            right: 4px;
            top: 4px;
            display: inline-block;
            font-size: 13px;
            font-weight: 700;
            line-height: 20px;
            color: #333;
            white-space: nowrap;
            vertical-align: middle;
            cursor: pointer;
            background-color: #eee;
            background-image: linear-gradient(#fcfcfc, #eee);
            border: 1px solid #d5d5d5;
            border-radius: 3px;
            -webkit-user-select: none;
            -moz-user-select: none;
            -ms-user-select: none;
            user-select: none;
            -webkit-appearance: none
        }

        .snippet:hover > .copybtn, .snippet > .copybtn:focus, .msgcopydiv:hover > .copybtn, .msgcopydiv > .copybtn:focus {
            opacity: 1
        }

        @media screen and (max-width: 768px) {
            .snippet > .copybtn {
                opacity: 0.4
            }

            .msgcopydiv > .copybtn {
            }
        }

        .userinfo > .msgcopydiv {
            margin-left: 30px
        }

        .aiinfo > .msgcopydiv {
            margin-right: 30px
        }

        .userinfo > .msgcopydiv > .copybtn {
            top: auto;
            bottom: 4px;
            left: -30px;
            right: auto
        }

        .aiinfo > .msgcopydiv > .copybtn {
            top: auto;
            bottom: 4px;
            left: auto;
            right: -30px
        }

        /* 以下copybtn (btn) by https://primer.style/css/components/tooltips https://clipboardjs.com/bower_components/primer-css/css/primer.css */
        .copybtn:focus {
            text-decoration: none;
            border-color: #51a7e8;
            outline: none;
            box-shadow: 0 0 5px rgba(81, 167, 232, .5)
        }

        .copybtn:focus:hover, .copybtn.selected:focus {
            border-color: #51a7e8
        }

        .copybtn:hover, .copybtn:active {
            text-decoration: none;
            background-color: #ddd;
            background-image: linear-gradient(#eee, #ddd);
            border-color: #ccc
        }

        .copybtn:active, .copybtn.selected {
            background-color: #dcdcdc;
            background-image: none;
            border-color: #b5b5b5;
            box-shadow: inset 0 2px 4px rgba(0, 0, 0, .15)
        }

        .copybtn.selected:hover {
            background-color: #cfcfcf
        }

        .copybtn:disabled, .copybtn:disabled:hover, .copybtn.disabled, .copybtn.disabled:hover {
            color: rgba(102, 102, 102, .5);
            cursor: default;
            background-color: rgba(229, 229, 229, .5);
            background-image: none;
            border-color: rgba(197, 197, 197, .5);
            box-shadow: none
        }

        /* 以上 copybtn (btn)  */
        .snippet .tooltipped-w:after {
            background: #6e7681
        }

        .snippet .tooltipped-w:before {
            border-left-color: #6e7681
        }

        /* 以下 Tooltips by https://primer.style/css/components/tooltips https://clipboardjs.com/bower_components/primer-css/css/primer.css */
        .tooltipped {
            position: relative
        }

        .tooltipped:after {
            position: absolute;
            z-index: 1000000;
            display: none;
            padding: 5px 8px;
            font: normal normal 11px/1.5 Helvetica, arial, nimbussansl, liberationsans, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol";
            color: #fff;
            text-align: center;
            text-decoration: none;
            text-shadow: none;
            text-transform: none;
            letter-spacing: normal;
            word-wrap: break-word;
            white-space: pre;
            pointer-events: none;
            content: attr(aria-label);
            background: rgba(0, 0, 0, .8);
            border-radius: 3px;
            -webkit-font-smoothing: subpixel-antialiased
        }

        .tooltipped:before {
            position: absolute;
            z-index: 1000001;
            display: none;
            width: 0;
            height: 0;
            color: rgba(0, 0, 0, .8);
            pointer-events: none;
            content: "";
            border: 5px solid transparent
        }

        .tooltipped:hover:before, .tooltipped:hover:after, .tooltipped:active:before, .tooltipped:active:after, .tooltipped:focus:before, .tooltipped:focus:after {
            display: inline-block;
            text-decoration: none
        }

        .tooltipped-n:after, .tooltipped-ne:after, .tooltipped-nw:after {
            right: 50%;
            bottom: 100%;
            margin-bottom: 5px
        }

        .tooltipped-n:before, .tooltipped-ne:before, .tooltipped-nw:before {
            top: -5px;
            right: 50%;
            bottom: auto;
            margin-right: -5px;
            border-top-color: rgba(0, 0, 0, .8)
        }

        .tooltipped-ne:after {
            right: auto;
            left: 50%;
            margin-left: -15px
        }

        .tooltipped-nw:after {
            margin-right: -15px
        }

        .tooltipped-n:after {
            -webkit-transform: translateX(50%);
            -ms-transform: translateX(50%);
            transform: translateX(50%)
        }

        .tooltipped-w:after {
            right: 100%;
            bottom: 50%;
            margin-right: 5px;
            -webkit-transform: translateY(50%);
            -ms-transform: translateY(50%);
            transform: translateY(50%)
        }

        .tooltipped-w:before {
            top: 50%;
            bottom: 50%;
            left: -5px;
            margin-top: -5px;
            border-left-color: rgba(0, 0, 0, .8)
        }

        .tooltipped-e:after {
            bottom: 50%;
            left: 100%;
            margin-left: 5px;
            -webkit-transform: translateY(50%);
            -ms-transform: translateY(50%);
            transform: translateY(50%)
        }

        .tooltipped-e:before {
            top: 50%;
            right: -5px;
            bottom: 50%;
            margin-top: -5px;
            border-right-color: rgba(0, 0, 0, .8)
        }

        /* 以上 Tooltips */
        /* 以上 一键复制相关 */
        #app {
            display: flex;
            flex-flow: column;
            margin: 1;
            white-space: pre-wrap
        }

        /* 以下 弹窗 */
        .dialog-wrapper {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 900100
        }

        .dialog-wrapper-hide {
            display: none
        }

        /* .dialog{width:95%;max-width:800px;height:auto;max-height:82%;overflow:scroll;border-radius:10px;z-index:900101;position:fixed;top:calc(50% - 14.5px);left:calc(50% - 0.5px);transform: translate(calc(-50% + 0.5px), calc(-50% + 0.5px));background-color:#ededed;box-shadow:0px 0px 10px rgba(0,0,0,0.3)} */
        .dialog {
            width: 95%;
            max-width: 800px;
            height: 78%;
            max-height: 1000px;
            overflow: hidden;
            border-radius: 10px;
            z-index: 900101;
            position: fixed;
            background-color: #ededed;
            box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3)
        }

        .dialog-header {
            position: relative;
            height: 40px;
            line-height: 40px;
            padding: 0 20px;
            background-color: #f5f5f5;
            border-top-left-radius: 10px;
            border-top-right-radius: 10px
        }

        .dialog-bottom {
            position: relative;
            height: 15px;
            line-height: 15px;
            border-bottom-left-radius: 10px;
            border-bottom-right-radius: 10px
        }

        .dialog-close {
            line-height: 20px;
            color: #333333;
            position: absolute;
            top: 10px;
            right: 10px;
            width: 20px;
            height: 20px;
            cursor: pointer
        }

        .dialog-title {
            font-size: 14px;
            line-height: 20px;
            color: #333333;
            position: absolute;
            top: 10px;
            align-items: center;
            justify-content: center;
            display: flex;
            -webkit-text-size-adjust: 100%;
        }

        .dialog-scroll {
            position: relative;
            width: 100%;
            height: calc(100% - 54.5px);
            overflow: auto;
            scrollbar-width: thin;
        }

        .dialog-content {
            padding: 20px;
            display: flex;
            flex-wrap: wrap;
            justify-content: space-between;
            align-items: center
        }

        .dialog-content .btn, .dialog-content .dh-input {
            margin: 0 2px
        }

        .dialog-content .panelrow {
            height: 26px;
            margin-bottom: 4px
        }

        .dialog-content .panelrow > :last-child {
            margin-right: 2px
        }

        /*.panelgroup- box-shadow> 1px 2px 9px 2.5px rgba(201, 201, 201, 0.6);  .panelgroup-shadow> box-shadow: 1px 1.5px 7.5px 2.5px rgba(201, 201, 201, 0.66); */
        .dialog-content .panelgroup {
            width: 100%;
            box-shadow: 0 3px 8px 0 rgba(20, 20, 20, 0.16), 0 0 1px 1px rgba(20, 20, 20, 0.07);
            padding: 5px 6px 4px 8px;
            border-radius: 5px;
            margin-bottom: 12px
        }

        .dialog-content .panelgroup-head {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 5px;
            padding-bottom: 1px;
            border-bottom: 1px solid rgb(220, 220, 220, 0.5)
        }

        .dialog-content .panelgroup-head > span {
            color: #666666;
            font-size: 12px
        }

        .dialog-content .panelgroup-shadow {
            box-shadow: 0 3px 8px 2px rgba(20, 20, 20, 0.14), 0 0 1px 1px rgba(20, 20, 20, 0.065);
        }

        .dialog-content span {
            font-size: 12px;
            color: #333333;
            margin-left: 4px;
            line-height: 18px;
            -webkit-text-size-adjust: 100%;
        }

        .dialog-mask {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            z-index: -1
        }

        .panelgroup-chatdata-selected {
            box-shadow: 0 0 0.5px 1px rgba(26, 168, 50, 0.8) !important;
            background: linear-gradient(rgba(26, 168, 50, 0.015), rgba(26, 168, 50, 0.15));
        }

        .panelgroup-chatdata-selected-transparent {
            box-shadow: 0 0 0.5px 2px rgba(26, 168, 50, 1) !important;
            background: linear-gradient(rgba(26, 168, 50, 0.005), rgba(26, 168, 50, 0.05));
        }

        .control-container {
            width: auto;
            margin-right: 6px
        }

        .control-bg {
            padding: 2px 5px;
            width: auto;
            height: auto;
            background: #fcfcfc;
            border-radius: 5px;
            display: flex
        }

        .slider {
            -webkit-appearance: none !important;
            -moz-appearance: none;
            appearance: none;
            width: 100%;
            height: 2px;
            margin: 9px 4px;
            padding: 0px 2px;
            background: linear-gradient(to bottom, #17d51e, #179e16);
            border: none;
            border-radius: 2px;
            outline: none;
            -webkit-transition: .2s;
            transition: opacity .2s
        }

        .slider::-webkit-slider-thumb {
            -webkit-appearance: none !important;
            appearance: none;
            width: 12px;
            height: 17px;
            border: none;
            background: linear-gradient(to bottom, #17d51e, #179e16);
            border-radius: 4px;
            cursor: pointer
        }

        .slider::-moz-range-thumb {
            -webkit-appearance: none !important;
            -moz-appearance: none !important;
            appearance: none;
            width: 12px;
            height: 17px;
            border: none;
            background: linear-gradient(to bottom, #17d51e, #179e16);
            border-radius: 4px;
            cursor: pointer
        }

        .slider::-ms-thumb {
            -webkit-appearance: none !important;
            appearance: none;
            width: 12px;
            height: 17px;
            border: none;
            background: linear-gradient(to bottom, #17d51e, #179e16);
            border-radius: 4px;
            cursor: pointer
        }

        .slider::-webkit-slider-thumb:hover, .slider::-webkit-slider-thumb:focus {
            background: linear-gradient(to bottom, #1ac83c, #179b16);
            outline: none
        }

        .slider::-moz-range-thumb:hover, .slider::-moz-range-thumb:focus {
            background: linear-gradient(to bottom, #1ac83c, #179b16);
            outline: none
        }

        .slider::-ms-thumb:hover, .slider::-ms-thumb:focus {
            background: linear-gradient(to bottom, #1ac83c, #179b16);
            outline: none
        }

        .slider::-webkit-slider-thumb:active {
            background: linear-gradient(to bottom, #1ac83c, #179b16);
        }

        .slider::-moz-range-thumb:active {
            background: linear-gradient(to bottom, #1ac83c, #179b16);
        }

        .slider::-ms-thumb:active {
            background: linear-gradient(to bottom, #1ac83c, #179b16);
        }

        @media only screen and (max-width: 768px) {
            .slider::-webkit-slider-thumb {
                width: 25px;
                height: 18px
            }

            .slider::-moz-range-thumb {
                width: 25px;
                height: 18px
            }

            .slider::-ms-thumb {
                width: 25px;
                height: 18px
            }
        }

        .slider:disabled::-webkit-slider-thumb {
            background: linear-gradient(to bottom, #d7ebd7, #c3d6c2);
            cursor: not-allowed
        }

        .slider:disabled::-moz-range-thumb {
            background: linear-gradient(to bottom, #d7ebd7, #c3d6c2);
            cursor: not-allowed
        }

        .slider:disabled::-ms-thumb {
            background: linear-gradient(to bottom, #d7ebd7, #c3d6c2);
            cursor: not-allowed
        }

        .slider:disabled {
            background: linear-gradient(to bottom, #d7ebd7, #c3d6c2);
            cursor: not-allowed
        }

        #dialog-settings .dh-input, #dialog-promptgenerator .dh-input {
            font-size: 12px;
            color: #333333;
            background-color: #fcfcfc
        }

        #dialog-promptgenerator .dh-input {
            font-size: 13px
        }

        #loading-mask button {
            margin: 30px;
            display: inline-block;
            padding: 8px 16px;
            font-size: 14px;
            text-align: center;
            color: #333;
            background-color: #fdffb4;
            border: 3px solid #555;
            border-radius: 10px;
            cursor: pointer
        }

        /* 以上 弹窗 */
        /* 以下 消息通知 tooltips */
        .notification {
            position: relative;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 900900
        }

        .notification-wrapper {
            max-width: 600px;
            overflow: hidden;
            position: fixed;
            transform: translate(-50%, -50%);
            background-color: #fdffb4;
            box-shadow: 0 0 8px rgba(191, 169, 16, 0.3);
            border: 1px solid rgba(166, 152, 27, 0.45);
            z-index: 900902
        }

        .notification-content {
            color: #664909;
            font-size: 13px;
            line-height: 1.6;
            -webkit-text-size-adjust: 100%;
        }

        .notification-content-center {
            display: block;
            text-align: start;
            padding-left: 6px;
            margin: 5px 10px 0px 10px;
            word-break: break-word
        }

        .notification-content-bottom {
            display: flex;
            text-align: center;
            padding-left: 0;
            margin: 5px 11px 5px 11px;
            word-break: break-word
        }

        .notification-center {
            width: 82%;
            min-width: 160px;
            bottom: auto;
            left: 50%;
            top: 50%;
            right: auto;
            border-radius: 10px
        }

        .notification-bottom {
            width: auto;
            min-width: 45px;
            bottom: calc(0% + 4.5px);
            left: 50%;
            top: auto;
            right: auto;
            border-radius: 6px;
            animation: slideIn 0.4s ease-out forwards
        }

        .notification-mask {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.65);
            z-index: 900901
        }

        @keyframes slideIn {
            from {
                transform: translate(-50%, -3px);
                opacity: 0
            }
            to {
                transform: translate(-50%, -18px);
                opacity: 1
            }
        }

        /* 以上 消息通知 tooltips */
    </style>
    <!-- 以上为css样式  -->
</head>


<body>
<!-- #app -->
<div id="app">

    <!-- 加载网页时的遮罩 Page Loading - Mask -->
    <div id="loading-mask" class="dialog-wrapper" style="z-index: 999999;">
        <div>加载中<br>Loading</div>
    </div>

    <!-- 主界面-聊天对话 Main interface - Chat  -->
    <div style="width:100%;">
        <div>
            <div v-for="(x,i) in msgList" :key="i">

                <!-- 系统提示消息-->
                <div v-if="x.prompt" style=" margin: 12px;	text-align: center;font-size: 12px; ">
                    <span>系统消息：{{x.msg}}</span>
                </div>

                <!-- Me / User WechatStyleDatetime -->
                <span v-if="!x.prompt && x.my " class="wechatstyle-datetime"
                      :style="{ display: (settingsTime_WechatStyle && x.wechatstyledatetime != null) ? 'block' : 'none', marginBottom: settingsTime_Message == 3 ? '27px' : '17px', fontSize: settingsFontSize * 0.9 + 'px' }"> {{x.wechatstyledatetime}}</span>

                <!-- Me / User Message Datetime -->
                <!--                <div v-if="!x.prompt &&x.my " :data-hr="x.hr" class="msgdatetimediv"-->
                <!--                     :style="{ display: settingsTime_Message == 1 ? 'none' : 'block' }"><span class="usermsg-datetime">{{x.datetime}}</span>-->
                <!--                </div>-->

                <!-- AI Message Datetime -->
                <div v-if="!x.prompt &&!x.my " :data-hr="x.hr" class="msgdatetimediv">
                    <span class="aimsg-datetime"
                          :style="{ display: settingsTime_Message == 1 ? 'none' : 'block' }">{{x.nickName}}</span>
                </div>

                <!-- Me / User Message -->
                <div v-if="!x.prompt &&x.my" class="userinfo"
                     :style="{ marginTop : settingsTime_Message == 3 ? '27px' : '17px' }">
                    <div class="chat-img btnzoom-a" @click="isShowDialog_ChatList = true"
                         :title="settingsLanguage == 'cn' ? '打开对话列表' : 'Open Chat List'">
                        <img :src="userAvatarURL">
                    </div>
                    <div class="justify-end msgcopydiv msgdiv">
                        <div v-html="x.msg" class="usermsg" :style="{ fontSize: settingsFontSize + 'px' }">
                        </div>
                    </div>
                </div>

                <!-- AI Message -->
                <div v-if="!x.prompt &&!x.my " class="aiinfo"
                     :style="{ marginTop : settingsTime_Message == 3 ? '27px' : '17px' }">
                    <div class="chat-img btnzoom-a" :style="{ display: isgptGreenSvgShow ? 'none' : 'flex' }"
                         @click="isShowDialog_ChatList = true"
                         :title="settingsLanguage == 'cn' ? '打开对话列表' : 'Open Chat List'">
                        <img :src="gptAvatarURL">
                    </div>
                    <div class="chat-img chat-green-svg btnzoom-a"
                         :style="{ display: isgptGreenSvgShow ? 'flex' : 'none'}" @click="isShowDialog_ChatList = true"
                         :title="settingsLanguage == 'cn' ? '打开对话列表' : 'Open Chat List'">
                        <svg width="27" height="27" viewBox="0 0 41 41" fill="none" xmlns="http://www.w3.org/2000/svg"
                             stroke-width="1.5" role="img">
                            <path d="M37.5324 16.8707C37.9808 15.5241 38.1363 14.0974 37.9886 12.6859C37.8409 11.2744 37.3934 9.91076 36.676 8.68622C35.6126 6.83404 33.9882 5.3676 32.0373 4.4985C30.0864 3.62941 27.9098 3.40259 25.8215 3.85078C24.8796 2.7893 23.7219 1.94125 22.4257 1.36341C21.1295 0.785575 19.7249 0.491269 18.3058 0.500197C16.1708 0.495044 14.0893 1.16803 12.3614 2.42214C10.6335 3.67624 9.34853 5.44666 8.6917 7.47815C7.30085 7.76286 5.98686 8.3414 4.8377 9.17505C3.68854 10.0087 2.73073 11.0782 2.02839 12.312C0.956464 14.1591 0.498905 16.2988 0.721698 18.4228C0.944492 20.5467 1.83612 22.5449 3.268 24.1293C2.81966 25.4759 2.66413 26.9026 2.81182 28.3141C2.95951 29.7256 3.40701 31.0892 4.12437 32.3138C5.18791 34.1659 6.8123 35.6322 8.76321 36.5013C10.7141 37.3704 12.8907 37.5973 14.9789 37.1492C15.9208 38.2107 17.0786 39.0587 18.3747 39.6366C19.6709 40.2144 21.0755 40.5087 22.4946 40.4998C24.6307 40.5054 26.7133 39.8321 28.4418 38.5772C30.1704 37.3223 31.4556 35.5506 32.1119 33.5179C33.5027 33.2332 34.8167 32.6547 35.9659 31.821C37.115 30.9874 38.0728 29.9178 38.7752 28.684C39.8458 26.8371 40.3023 24.6979 40.0789 22.5748C39.8556 20.4517 38.9639 18.4544 37.5324 16.8707ZM22.4978 37.8849C20.7443 37.8874 19.0459 37.2733 17.6994 36.1501C17.7601 36.117 17.8666 36.0586 17.936 36.0161L25.9004 31.4156C26.1003 31.3019 26.2663 31.137 26.3813 30.9378C26.4964 30.7386 26.5563 30.5124 26.5549 30.2825V19.0542L29.9213 20.998C29.9389 21.0068 29.9541 21.0198 29.9656 21.0359C29.977 21.052 29.9842 21.0707 29.9867 21.0902V30.3889C29.9842 32.375 29.1946 34.2791 27.7909 35.6841C26.3872 37.0892 24.4838 37.8806 22.4978 37.8849ZM6.39227 31.0064C5.51397 29.4888 5.19742 27.7107 5.49804 25.9832C5.55718 26.0187 5.66048 26.0818 5.73461 26.1244L13.699 30.7248C13.8975 30.8408 14.1233 30.902 14.3532 30.902C14.583 30.902 14.8088 30.8408 15.0073 30.7248L24.731 25.1103V28.9979C24.7321 29.0177 24.7283 29.0376 24.7199 29.0556C24.7115 29.0736 24.6988 29.0893 24.6829 29.1012L16.6317 33.7497C14.9096 34.7416 12.8643 35.0097 10.9447 34.4954C9.02506 33.9811 7.38785 32.7263 6.39227 31.0064ZM4.29707 13.6194C5.17156 12.0998 6.55279 10.9364 8.19885 10.3327C8.19885 10.4013 8.19491 10.5228 8.19491 10.6071V19.808C8.19351 20.0378 8.25334 20.2638 8.36823 20.4629C8.48312 20.6619 8.64893 20.8267 8.84863 20.9404L18.5723 26.5542L15.206 28.4979C15.1894 28.5089 15.1703 28.5155 15.1505 28.5173C15.1307 28.5191 15.1107 28.516 15.0924 28.5082L7.04046 23.8557C5.32135 22.8601 4.06716 21.2235 3.55289 19.3046C3.03862 17.3858 3.30624 15.3413 4.29707 13.6194ZM31.955 20.0556L22.2312 14.4411L25.5976 12.4981C25.6142 12.4872 25.6333 12.4805 25.6531 12.4787C25.6729 12.4769 25.6928 12.4801 25.7111 12.4879L33.7631 17.1364C34.9967 17.849 36.0017 18.8982 36.6606 20.1613C37.3194 21.4244 37.6047 22.849 37.4832 24.2684C37.3617 25.6878 36.8382 27.0432 35.9743 28.1759C35.1103 29.3086 33.9415 30.1717 32.6047 30.6641C32.6047 30.5947 32.6047 30.4733 32.6047 30.3889V21.188C32.6066 20.9586 32.5474 20.7328 32.4332 20.5338C32.319 20.3348 32.154 20.1698 31.955 20.0556ZM35.3055 15.0128C35.2464 14.9765 35.1431 14.9142 35.069 14.8717L27.1045 10.2712C26.906 10.1554 26.6803 10.0943 26.4504 10.0943C26.2206 10.0943 25.9948 10.1554 25.7963 10.2712L16.0726 15.8858V11.9982C16.0715 11.9783 16.0753 11.9585 16.0837 11.9405C16.0921 11.9225 16.1048 11.9068 16.1207 11.8949L24.1719 7.25025C25.4053 6.53903 26.8158 6.19376 28.2383 6.25482C29.6608 6.31589 31.0364 6.78077 32.2044 7.59508C33.3723 8.40939 34.2842 9.53945 34.8334 10.8531C35.3826 12.1667 35.5464 13.6095 35.3055 15.0128ZM14.2424 21.9419L10.8752 19.9981C10.8576 19.9893 10.8423 19.9763 10.8309 19.9602C10.8195 19.9441 10.8122 19.9254 10.8098 19.9058V10.6071C10.8107 9.18295 11.2173 7.78848 11.9819 6.58696C12.7466 5.38544 13.8377 4.42659 15.1275 3.82264C16.4173 3.21869 17.8524 2.99464 19.2649 3.1767C20.6775 3.35876 22.0089 3.93941 23.1034 4.85067C23.0427 4.88379 22.937 4.94215 22.8668 4.98473L14.9024 9.58517C14.7025 9.69878 14.5366 9.86356 14.4215 10.0626C14.3065 10.2616 14.2466 10.4877 14.2479 10.7175L14.2424 21.9419ZM16.071 17.9991L20.4018 15.4978L24.7325 17.9975V22.9985L20.4018 25.4983L16.071 22.9985V17.9991Z"
                                  fill="currentColor">
                            </path>
                        </svg>
                    </div>
                    <div class="msgcopydiv msgdiv">
                        <div v-html="x.msg" class="aimsg" :style="{ fontSize: settingsFontSize + 'px' }">
                        </div>
                    </div>
                </div>
            </div>

            <!-- 空白块 布局/定位用 For position -->
            <div class="bottom-position"></div>
        </div>
    </div>
    <!-- End: 主界面-聊天对话 Main interface - Chat -->


    <!-- 主界面-底部  Main interface - Bottom  -->
    <div class="flex-column-center">
        <!-- First row -->
        <div class="panelrow" style="margin-top: 6px;height: 40px;">
            <button @click="isShowDialog_PromptGenerator = true" class="btn"
                    :class="{ 'orangeredbtn': !(gptSystemPromptReadOnly.trim() == '') }"
                    :disabled="(gptSystemPrompt_hidden.trim() != '')"
                    :title="gptSystemPrompt_hidden.trim() == '' ? (settingsLanguage == 'cn' ? '打开提示词窗口' : 'Open Prompt Generator') : (settingsLanguage == 'cn' ? '功能已被禁用' : 'The feature has been disabled') "
                    style="margin-left:12px;margin-right:-1px;border-radius: 6px 0px 0px 6px;line-height: 38px; width: 27px;height: 38px;padding: 0 8px;z-index: 900001;">
                <div>
                    <svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" width="24"
                         height="24" viewBox="0 0 24 24">
                        <path clip-rule="evenodd"
                              d="M14.526 6.10576C15.0265 6.33917 15.2667 6.88343 15.0625 7.3214L9.88541 18.4237C9.68118 18.8616 9.10985 19.0275 8.60931 18.7941C8.10877 18.5607 7.86857 18.0164 8.0728 17.5784L13.2499 6.47616C13.4541 6.03819 14.0254 5.87235 14.526 6.10576Z"
                              fill="currentColor" fill-rule="evenodd"/>
                    </svg>
                    </svg>
                </div>
            </button>
            <textarea @click="scrollToBottomViewClick" @blur="textareaChatInputBox_blur"
                      @focus="textareaChatInputBox_focus" @keydown="textareaMobileInputCompatibility"
                      @keydown.enter.exact="textareaEnter" @keydown.ctrl.enter.exact="newLine"
                      @keydown.meta.enter.exact="newLine" v-model="msg" type="text"
                      class="dh-input textareachatinputbox"
                      style="margin-left:0px;height: 38px;min-height: 38px;max-height: 38px;border-radius: 0px 6px 6px 0px; z-index:900000"
                      placeholder="" enterkeyhint="send"></textarea>
            <button @click="sendMsg();" :disabled="btnDisabledState_Sending" class="btn"
                    :title="settingsLanguage == 'cn' ? '发送' : 'Send'"
                    style="line-height: 38px; height: 38px;font-size: 14px;padding: 0 15px;">{{sentext}}
            </button>
        </div>

        <!-- Second row - Left -->
        <div class="panelrow" style="margin-bottom: 7px;">
            <button @click="clearContext" :disabled="btnDisabledState_Clear" class="btn"
                    style="margin-left:12px;margin-right: 4px;" :title="settingsLanguage == 'cn' ? '清除' : 'Clear'">
                <div>
                    <svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none"
                         stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
                        <polyline points="3 6 5 6 21 6"></polyline>
                        <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
                        <line x1="10" y1="11" x2="10" y2="17"></line>
                        <line x1="14" y1="11" x2="14" y2="17"></line>
                    </svg>
                </div>
                <span v-if="settingsLanguage == 'cn' " :style="{ display: isShowQACountLabel ? 'flex' : 'none' }">&nbsp;记忆</span>
                <span v-else :style="{ display: isShowQACountLabel ? 'flex' : 'none' }">&nbsp;Context</span>
                <span>:&nbsp;{{succQA_Count}}</span>
            </button>
            <button disabled class="btn"
                    style="margin-right:-10px;margin-left: 0px;padding-right: 15px;border-radius: 6px 0px 0px 6px;">
                <div :style="{ display: isShowTotaltokensSVG ? 'flex' : 'none' }">
                    <svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" width="25"
                         height="18" viewBox="0 0 25 19" fill="currentColor" stroke-width="0.02" stroke="currentColor"
                         stroke-linecap="round" stroke-linejoin="round">
                        <path d="m10.62109,1.66441c-1.05095,-0.60518 -2.43437,-0.9385 -3.89526,-0.9385c-1.46093,0 -2.84426,0.33332 -3.89526,0.9385c-1.12359,0.64701 -1.74652,1.53053 -1.75767,2.49024l-0.00051,0l0,10.75701c0,1.91873 2.48327,3.42172 5.65344,3.42172c3.17018,0 5.65349,-1.50299 5.65349,-3.42172l0,-10.74381l-0.00023,0c-0.00557,-0.96482 -0.62926,-1.85344 -1.758,-2.50345zm0.77473,6.82194c0,1.32648 -2.13857,2.44712 -4.66998,2.44712c-2.53136,0 -4.66994,-1.12064 -4.66994,-2.44712l0,-0.18096c1.01002,0.8957 2.70835,1.47278 4.66994,1.47278c1.96163,0 3.65992,-0.57714 4.66998,-1.47278l0,0.18096zm0,2.13876c0,1.32652 -2.13857,2.44716 -4.66998,2.44716c-2.53136,0 -4.66994,-1.12068 -4.66994,-2.44716l0,-0.18092c1.01002,0.89565 2.70835,1.47278 4.66994,1.47278c1.96163,0 3.65992,-0.57713 4.66998,-1.47278l0,0.18092zm-9.33992,1.95788c1.01002,0.8957 2.70835,1.47278 4.66993,1.47278c1.96164,0 3.65992,-0.57713 4.66998,-1.47283l0,0.18102c0,1.32647 -2.13857,2.44711 -4.66998,2.44711c-2.53136,0 -4.66993,-1.12064 -4.66993,-2.44711l0,-0.18097zm9.33992,-6.23549c0,1.32647 -2.13857,2.44711 -4.66998,2.44711c-2.53137,0.00005 -4.66994,-1.12063 -4.66994,-2.44711l0,-0.22269c1.01002,0.88937 2.70835,1.46238 4.66993,1.46238c1.96164,0 3.65992,-0.57306 4.66998,-1.46238l0,0.22269l0,0zm-8.07442,-3.83077c0.90403,-0.52061 2.11309,-0.80727 3.40444,-0.80727c1.29139,0 2.50046,0.28671 3.40449,0.80727c0.80423,0.46309 1.2655,1.06963 1.2655,1.66405c0,1.31336 -2.13857,2.4229 -4.66998,2.4229c-2.53136,0 -4.66994,-1.10954 -4.66994,-2.4229c0,-0.59441 0.46127,-1.20095 1.26549,-1.66405zm3.40444,14.83319c-2.53136,0 -4.66993,-1.11656 -4.66993,-2.43826l0,-0.18982c1.01002,0.89566 2.70835,1.47279 4.66993,1.47279c1.96164,0 3.65992,-0.57713 4.66998,-1.47279l0,0.18987c0,1.32166 -2.13862,2.43822 -4.66998,2.43822z"/>
                        <path d="m24.01826,10.37334c-0.00347,-0.9667 -0.62739,-1.85725 -1.75804,-2.50833c-1.05095,-0.60519 -2.43437,-0.9385 -3.89531,-0.9385c-1.46088,0 -2.84426,0.33332 -3.89526,0.9385c-1.1338,0.65286 -1.75818,1.54655 -1.75818,2.51633c0,0.00023 0,0.00047 0,0.00075l0,0l0,4.32772c0,1.91086 2.48326,3.40772 5.65344,3.40772c3.17018,0 5.65349,-1.49686 5.65349,-3.40772l0,-4.33653l-0.00014,0l0,0.00005zm-0.98337,2.17477c0,1.32648 -2.13857,2.44717 -4.66999,2.44717c-2.53137,0 -4.66993,-1.12065 -4.66993,-2.44717l0,-0.22269c1.01002,0.88937 2.7083,1.46238 4.66993,1.46238s3.65992,-0.57301 4.66999,-1.46238l0,0.22269zm-8.07442,-3.83077c0.90403,-0.52061 2.11309,-0.80727 3.40444,-0.80727c1.29135,0 2.50046,0.28672 3.40449,0.80727c0.80423,0.46309 1.2655,1.06964 1.2655,1.66405c0,1.31336 -2.13857,2.4229 -4.66999,2.4229c-2.53137,0 -4.66993,-1.10954 -4.66993,-2.4229c0.00005,-0.59441 0.46127,-1.20096 1.2655,-1.66405zm3.40444,8.41673c-2.53137,0 -4.66993,-1.11014 -4.66993,-2.42421l0,-0.20386c1.01002,0.89565 2.7083,1.47277 4.66993,1.47277s3.65992,-0.57708 4.66998,-1.47277l0,0.20386c0,1.31407 -2.13857,2.42421 -4.66998,2.42421z"/>
                    </svg>
                </div>
                <span v-if="settingsLanguage == 'cn' " :style="{ display: isShowTotaltokensLabel ? 'flex' : 'none' }">&nbsp;在线用户数量</span>
                <span v-else :style="{ display: isShowTotaltokensLabel ? 'flex' : 'none' }">&nbsp;Tokens</span>
                <span>:&nbsp;{{onlineUserNum}}</span>
            </button>
            <!-- 简化聊天记录 1111111111 Chat Simplifier Shortcut  -->
            <button disabled
                    :title="settingsLanguage == 'cn' ? '压缩上下文 (待开发)' : 'Chat Simplifier (Under development)'"
                    class="btn" style="margin-right:auto;padding-left:9px;border-radius: 0px 6px 6px 0px;">
                <div>
                    <svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" width="19"
                         height="19" fill="currentColor" stroke="currentColor" stroke-width="0.1" stroke-linecap="round"
                         stroke-linejoin="round">
                        <path d="m9.58259,6.35354c0.15086,0 0.26579,-0.05745 0.41661,-0.19392l2.39193,-2.22672c0.12211,-0.10776 0.18678,-0.23705 0.18678,-0.40226c0,-0.2945 -0.22266,-0.50999 -0.52437,-0.50999c-0.14364,0 -0.28011,0.06463 -0.39505,0.17239l-0.93378,0.93378l-0.61774,0.64649l0.05027,-1.20675l0,-2.3704c0,-0.30168 -0.26576,-0.55308 -0.57465,-0.55308c-0.30886,0 -0.56744,0.2514 -0.56744,0.55308l0,2.3704l0.0431,1.20675l-0.61774,-0.64649l-0.93378,-0.93378c-0.10776,-0.10776 -0.2514,-0.17239 -0.39508,-0.17239c-0.30886,0 -0.52434,0.21548 -0.52434,0.50999c0,0.16521 0.06463,0.2945 0.19392,0.40226l2.38478,2.22672c0.15082,0.13647 0.27294,0.19392 0.41658,0.19392zm-6.04805,5.46626l12.09611,0c1.19951,0 2.03274,-0.8117 2.03274,-1.91786l0,-0.73268c0,-1.10617 -0.83323,-1.91783 -2.03274,-1.91783l-12.09611,0c-1.19236,0 -2.03277,0.81167 -2.03277,1.91783l0,0.73268c0,1.10617 0.84041,1.91786 2.03277,1.91786zm0.15086,-1.08463c-0.64649,0 -1.04154,-0.431 -1.04154,-1.01283l0,-0.37349c0,-0.58182 0.39505,-1.01283 1.04154,-1.01283l11.79446,0c0.64639,0 1.04154,0.431 1.04154,1.01283l0,0.37349c0,0.58183 -0.39514,1.01283 -1.04154,1.01283l-11.79446,0zm5.8972,7.69295c0.30889,0 0.57465,-0.2514 0.57465,-0.55308l0,-2.37039l-0.05027,-1.20671l0.61774,0.64645l0.93378,0.93378c0.11493,0.10776 0.2514,0.17239 0.39505,0.17239c0.30171,0 0.52437,-0.20831 0.52437,-0.50998c0,-0.16521 -0.06466,-0.28732 -0.18678,-0.40223l-2.39193,-2.22675c-0.15082,-0.13647 -0.26576,-0.18674 -0.41661,-0.18674c-0.14365,0 -0.26576,0.05027 -0.41658,0.18674l-2.38478,2.22675c-0.12929,0.1149 -0.19392,0.23702 -0.19392,0.40223c0,0.30168 0.21549,0.50998 0.52434,0.50998c0.14368,0 0.28732,-0.06463 0.39508,-0.17239l0.93378,-0.93378l0.61774,-0.64645l-0.0431,1.20671l0,2.37039c0,0.30168 0.25858,0.55308 0.56744,0.55308z"/>
                    </svg>
                </div>
            </button>

            <!-- Second row - Right -->
            <button @click="undo" :disabled="btnDisabledState_Undo" class="btn"
                    style="margin-left:auto;margin-right:4px;width:45px;"
                    :title="settingsLanguage == 'cn' ? '撤销' : 'Undo'">
                <div>
                    <svg xmlns="http://www.w3.org/2000/svg" width="24px" height="23px" viewBox="0 0 24 24" fill="none"
                         stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M5 9.5C8.5 9.5 11.5 9.5 15 9.5C15.1615 9.5 19 9.5 19 13.5C19 18 15.2976 18 15 18C12 18 10 18 7 18"/>
                        <path d="M8.5 13C7.13317 11.6332 6.36683 10.8668 5 9.5C6.36683 8.13317 7.13317 7.36683 8.5 6"/>
                    </svg>
                </div>
            </button>
            <button @click="retry" :disabled="btnDisabledState_Undo" class="btn" style="margin-right:4px;width:45px;"
                    :title="settingsLanguage == 'cn' ? '重问' : 'Retry'">
                <div>
                    <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"
                         stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M17 2.1l4 4-4 4"/>
                        <path d="M3 12.2v-2a4 4 0 0 1 4-4h12.8M7 21.9l-4-4 4-4"/>
                        <path d="M21 11.8v2a4 4 0 0 1-4 4H4.2"/>
                    </svg>
                </div>
            </button>
            <button @click="openDialogTodo_Settings" class="btn" :class="{ 'redbtn': !btnDisabledState_CheckAPI }"
                    style="margin-right:4px;width:45px;" :title="settingsLanguage == 'cn' ? '设置' : 'Settings'">
                <div>
                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
                         stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <circle cx="12" cy="12" r="3"></circle>
                        <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
                    </svg>
                </div>
            </button>
            <button @click="openDialog_Help" class="btn bluebtn" style="width:45px;"
                    :title="settingsLanguage == 'cn' ? '帮助' : 'Help'">
                <div>
                    <svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none"
                         stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <circle cx="12" cy="12" r="10"></circle>
                        <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
                        <line x1="12" y1="17" x2="12.01" y2="17"></line>
                    </svg>
                </div>
            </button>
        </div>
    </div>
    <!-- End: 主界面-底部  Main interface - Bottom  -->


    <!-- dialog-wrapper A: 设置 Settings -->
    <div id="dialog-settings" class="dialog-wrapper" v-show="isShowDialog_Settings">
        <!-- A: dialog -->
        <div class="dialog" style="max-height: 800px;">

            <!-- A: 标题 header -->
            <div class="dialog-header">
                <div class="dialog-title" style="justify-content: space-between;width: calc(95% - 27px);">
                    <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none"
                         stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <circle cx="12" cy="12" r="3"></circle>
                        <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
                    </svg>
                    {{ settingsLanguage == 'cn' ? '&nbsp;设置' : '&nbsp;Settings' }}
                    <select @change="changeSimpleMode(true)"
                            class="dropdown minibtn minibtn-default bluedropdown btnzoom-b" v-model="isSimpleMode"
                            :style="{ marginLeft: settingsLanguage == 'cn' ? '5px' : '5px',marginTop: settingsLanguage == 'cn' ? '0.5px' : '1.5px', width: settingsLanguage == 'cn' ? ( isSimpleMode ? '64px' : '77px' ) : ( isSimpleMode ? '91px' : '111px' )}"
                            :title="settingsLanguage == 'cn' ? '当前LinGPT模式' : 'Current LinGPT Mode'">
                        <option v-if="settingsLanguage == 'cn'" v-for="option in simpleModeOptions_cn"
                                :value="option.state">{{ option.label }}
                        </option>
                        <option v-else v-for="option in simpleModeOptions_en" :value="option.state">{{ option.label }}
                        </option>
                    </select>
                    <span style="margin-left:auto" :title="settingsLanguage == 'cn' ? '当前版本' : 'Current Version'">v7.29&nbsp;&nbsp;</span>
                    <button class="btn btnzoom-a"
                            style="margin: 0px; padding: 0 3px;background: linear-gradient(to bottom, #fff, #fff);background-color: #fff;height: 20px;width: 20px; box-shadow: 0 0 0px 0.5px rgba(80, 80, 80, 0.08);"
                            :title="settingsLanguage == 'cn' ? '在GitHub上查看最新版本' : 'View the latest version on GitHub'">
                        <a href="https://github.com/lin2025/gpt3.5/" target="_blank">
                            <svg xmlns="http://www.w3.org/2000/svg" width="14" viewBox="0 0 16 16" fill="#1f2328">
                                <path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z"></path>
                            </svg>
                        </a>
                    </button>
                    <button class="btn btnzoom-a"
                            style="margin-left: 5px; padding: 0 3px;background: linear-gradient(to bottom, #fff, #fff);background-color: #fff;height: 20px;width: 20px; box-shadow: 0 0 0px 0.5px rgba(80, 80, 80, 0.08);"
                            :title="settingsLanguage == 'cn' ? '在Gitee上查看最新版本' : 'View the latest version on Gitee'">
                        <a href="https://gitee.com/GiteeKey/ruoyi-blog/" target="_blank">
                            <svg xmlns="http://www.w3.org/2000/svg" width="14" viewBox="0 0 24 24" fill="#c61d22">
                                <path d="m11.984,0a12,12 0 0 0 -11.984,12a12,12 0 0 0 12,12a12,12 0 0 0 12,-12a12,12 0 0 0 -12,-12a12,12 0 0 0 -0.016,0zm6.09,5.333c0.328,0 0.593,0.266 0.592,0.593l0,1.482a0.594,0.594 0 0 1 -0.593,0.592l-8.296,0c-0.982,0 -1.778,0.796 -1.778,1.778l0,5.63c0,0.327 0.266,0.592 0.593,0.592l5.63,0c0.982,0 1.778,-0.796 1.778,-1.778l0,-0.296a0.593,0.593 0 0 0 -0.592,-0.593l-4.15,0a0.592,0.592 0 0 1 -0.592,-0.592l0,-1.482a0.593,0.593 0 0 1 0.593,-0.592l6.815,0c0.327,0 0.593,0.265 0.593,0.592l0,3.408a4,4 0 0 1 -4,4l-8.741,0a0.593,0.593 0 0 1 -0.593,-0.593l0,-8.296a4.444,4.444 0 0 1 4.445,-4.444l8.296,0l0,-0.001z"/>
                            </svg>
                        </a>
                    </button>
                </div>

                <div class="dialog-close" @click="isShowDialog_Settings = false"
                     :title="settingsLanguage == 'cn' ? '关闭' : 'Close'">
                    <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"
                         style="fill: none; stroke: #333; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;">
                        <path d="M6 18L18 6M6 6l12 12"></path>
                    </svg>
                </div>
            </div>

            <!-- A: 滚动框架 scroll -->
            <div class="dialog-scroll">
                <!-- A: 主体内容 content -->
                <div class="dialog-content">

                    <!-- panelgroup: 接口密钥 API Key -->
                    <div class="panelgroup panelgroup-shadow">
                        <div class="panelgroup-head">
                            <span>{{ settingsLanguage == 'cn' ? 'OpenAI 接口密钥 / API Key ' : 'OpenAI API Key '
                                }}</span>
                            <button @click="showMiniHelp('A-CheckAPIKey')" class="btn minibtn bluebtn btnzoom-b"
                                    :style="{ marginRight: btnDisabledState_CheckAPI ? 'auto' : '2px' }"
                                    :title="settingsLanguage == 'cn' ? '功能说明' : 'Help'">?
                            </button>
                            <span :style="{ display: btnDisabledState_CheckAPI ? 'none' : 'block' }"
                                  style="margin-right: auto;color: #e63214;font-size: 11px;">{{ settingsLanguage == 'cn' ? '*必填' : '*Required'
                                }}</span>
                            <!-- 轮询 / 自动轮换API Key / Auto Key Rotation -->
                            <button @click="clickOpenAutoKeyRotation" class="btn minibtn minibtn-default btnzoom-b"
                                    :class="{ 'orangereddropdown': (apiCheckedData.length > 0) }"
                                    :style="{ marginRight: settingsLanguage == 'cn' ? '-6px' : '-6px'}"
                                    style="display: flex;"
                                    :title="settingsLanguage == 'cn' ? '管理自动轮换API Key' : 'Auto Key Rotation Management'">
                                <div>
                                    <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24"
                                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                         stroke-linejoin="round">
                                        <path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"/>
                                    </svg>
                                </div>
                                &nbsp;{{ settingsLanguage == 'cn' ? '自动轮换API Key' : 'Auto Key Rotation' }}
                            </button>
                            <!-- 该功能已被隐藏 The feature has been hidden:   Session Key不是固定的，每次登录退出都会变化。 The Session Key is not static and changes with each login and logout. -->
                            <button style="display: none;" @click="sendRequest_usingSessionKey"
                                    class="btn minibtn minibtn-default btnzoom-b"
                                    :style="{ marginRight: settingsLanguage == 'cn' ? '-4px' : '-5px'}"
                                    :title="settingsLanguage == 'cn' ? '恢复默认' : 'Restore Default'">
                                <div>
                                    <svg xmlns="http://www.w3.org/2000/svg" width="13px" height="13px"
                                         viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"
                                         stroke-miterlimit="10">
                                        <circle cx="9.5106" cy="9.42613" r="7.64"/>
                                        <path d="m6.6506,12.28613l3.34,0a1.43,1.43 0 0 0 1.43,-1.43l0,0a1.43,1.43 0 0 0 -1.43,-1.43l-0.95,0a1.44,1.44 0 0 1 -1.39,-1.44l0,0a1.44,1.44 0 0 1 1.44,-1.43l3.34,0"/>
                                        <line x1="9.5106" x2="9.5106" y1="4.64613" y2="6.55613"/>
                                        <line x1="9.5106" x2="9.5106" y1="12.28613" y2="14.19613"/>
                                        <line transform="rotate(90 18.2202 18.6877)" x1="14.70164" x2="21.73886"
                                              y1="22.20633" y2="15.16912"/>
                                    </svg>
                                </div>
                                {{ settingsLanguage == 'cn' ? 'Session Key' : 'Session Key' }}
                            </button>
                        </div>

                        <div class="panelrow">
                            <!-- 一键复制API Key  Cpoy API Key -->
                            <button class="copybtn btncopyapikey" data-clipboard-nextelementsibling=""
                                    style="opacity:1;position:relative;flex-shrink: 0;height: 25px;top:0;right:0;margin-right:-4px;border-radius: 6px 0px 0px 6px;border: 1px solid #DDD;width: 25px"
                                    :title="settingsLanguage == 'cn' ? '一键复制' : 'One-Click Copy'">
                                <div>
                                    <svg width="14" height="17" class="clippy" fill="currentColor">
                                        <path d="m2.36373,12.61912l4.12233,0l0,1.03058l-4.12233,0l0,-1.03058zm5.15291,-6.18349l-5.15291,0l0,1.03058l5.15291,0l0,-1.03058zm2.06116,3.09174l0,-2.06116l-3.09174,3.09174l3.09174,3.09174l0,-2.06116l5.15291,0l0,-2.06116l-5.15291,0zm-4.63761,-1.03058l-2.57645,0l0,1.03058l2.57645,0l0,-1.03058zm-2.57645,3.09174l2.57645,0l0,-1.03058l-2.57645,0l0,1.03058zm9.27523,1.03058l1.03058,0l0,2.06116c-0.0161,0.28985 -0.11272,0.53139 -0.30595,0.72463s-0.43478,0.28985 -0.72463,0.30595l-10.30581,0c-0.5636,0 -1.03058,-0.46698 -1.03058,-1.03058l0,-11.33639c0,-0.5636 0.46698,-1.03058 1.03058,-1.03058l3.09175,0c0,-1.1433 0.91786,-2.06116 2.06116,-2.06116s2.06116,0.91786 2.06116,2.06116l3.09174,0c0.5636,0 1.03058,0.46698 1.03058,1.03058l0,5.15291l-1.03058,0l0,-3.09174l-10.30581,0l0,9.27523l10.30581,0l0,-2.06116zm-9.27523,-8.24465l8.24465,0c0,-0.5636 -0.46698,-1.03058 -1.03058,-1.03058l-1.03058,0c-0.5636,0 -1.03058,-0.46698 -1.03058,-1.03058s-0.46698,-1.03058 -1.03058,-1.03058s-1.03058,0.46698 -1.03058,1.03058s-0.46698,1.03058 -1.03058,1.03058l-1.03058,0c-0.5636,0 -1.03058,0.46698 -1.03058,1.03058z">
                                        </path>
                                    </svg>
                                </div>
                            </button>
                            <!-- 隐藏的API Key，这个可被复制内容  hide : Function: Data can be read by "Copy Btn" -->
                            <input class="inputghostforcopyapikey" type="text" v-model="api"
                                   style="opacity:0;height:1px;width:1px;overflow:hidden;margin:0;padding:0;border:none;"/>
                            <!-- API Key输入框。 因为type="password" ，不可被复制。 Because type="password", it cannot be copied by "Copy Btn". -->
                            <input @blur="inputapiblur" @focus="inputapifocus" @input="inputapichange(false)"
                                   :style="{ color: btnDisabledState_CheckAPI ? '#cccccc' : '#191919' }" v-model="api"
                                   type="password" class="dh-input inputapikey" style="border-radius: 0px;"
                                   :placeholder="settingsLanguage == 'cn' ? 'API Key 粘贴到这里' : 'Paste your API Key here.' "/>
                            <!-- 验证API Key Check API Key -->
                            <button @click="checkAPIbtn" :disabled="btnDisabledState_CheckAPI" class="btn"
                                    :class="{ 'redbtn': !btnDisabledState_CheckAPI }"
                                    style="border-radius: 0px 6px 6px 0px;margin-left:-3px;">{{apibtntext}}
                            </button>
                            <!-- 1.查询OpenAI API余额 2.恢复API Key  1.Check OpenAI API balances 2.Restore Api Key-->
                            <button @click="sendRequest_or_restoreApiKeyInput" class="btn"
                                    :class="{ 'redbtn': !btnDisabledState_CheckAPI }"
                                    :style="{ padding: !btnDisabledState_CheckAPI ? '0 4px' : '0 8px' }"
                                    :title="settingsLanguage == 'cn' ? (btnDisabledState_CheckAPI ? '查询OpenAI API余额' : '取消修改 并恢复API Key') : (btnDisabledState_CheckAPI ? 'Check OpenAI API balances' : 'Cancel and Restore API Key')">
                                <div :style="{ display: btnDisabledState_CheckAPI ? 'flex' : 'none' }">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="21px" height="20px"
                                         viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"
                                         stroke-miterlimit="10">
                                        <circle cx="9.5106" cy="9.42613" r="7.64"/>
                                        <path d="m6.6506,12.28613l3.34,0a1.43,1.43 0 0 0 1.43,-1.43l0,0a1.43,1.43 0 0 0 -1.43,-1.43l-0.95,0a1.44,1.44 0 0 1 -1.39,-1.44l0,0a1.44,1.44 0 0 1 1.44,-1.43l3.34,0"/>
                                        <line x1="9.5106" x2="9.5106" y1="4.64613" y2="6.55613"/>
                                        <line x1="9.5106" x2="9.5106" y1="12.28613" y2="14.19613"/>
                                        <line transform="rotate(90 18.2202 18.6877)" x1="14.70164" x2="21.73886"
                                              y1="22.20633" y2="15.16912"/>
                                    </svg>
                                </div>
                                <div :style="{ display: btnDisabledState_CheckAPI ? 'none' : 'flex' }">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"
                                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                         stroke-linejoin="round">
                                        <circle cx="12" cy="12" r="10"></circle>
                                        <line x1="15" y1="9" x2="9" y2="15"></line>
                                        <line x1="9" y1="9" x2="15" y2="15"></line>
                                    </svg>
                                </div>
                            </button>
                        </div>
                    </div>


                    <!-- panelgroup: API设置  API Settings -->
                    <!--                    <div class="panelgroup panelgroup-shadow">-->
                    <!--                        <div class="panelgroup-head">-->
                    <!--                            <span>{{ settingsLanguage == 'cn' ? 'API设置' : 'API Settings' }}</span>-->
                    <!--                            <button @click="showMiniHelp('A-APISettings')" class="btn minibtn bluebtn btnzoom-b"-->
                    <!--                                    style="margin-right: auto;" :title="settingsLanguage == 'cn' ? '功能说明' : 'Help'">-->
                    <!--                                ?-->
                    <!--                            </button>-->
                    <!--                            <button @click="showMiniHelp('A-SmartMaxTokens')" class="btn minibtn orangeredbtn btnzoom-c"-->
                    <!--                                    style="width: 12px; height: 12px;"-->
                    <!--                                    :style="{ display: openSmartMaxTokens  ? 'block' : 'none', marginRight: settingsLanguage == 'cn' ? '-7px' : '-9px'}"-->
                    <!--                                    :title="settingsLanguage == 'cn' ? '功能说明' : 'Help'">?-->
                    <!--                            </button>-->
                    <!--                            <select @change="changeSmartMaxTokens" class="dropdown minibtn minibtn-default btnzoom-d"-->
                    <!--                                    :class="{ 'orangereddropdown': openSmartMaxTokens }" v-model="openSmartMaxTokens"-->
                    <!--                                    :style="{ marginRight: settingsLanguage == 'cn' ? '-9px' : '-13px'}"-->
                    <!--                                    :title="settingsLanguage == 'cn' ? '当前[Smart max_tokens]状态' : 'Current [Smart max_tokens] Status'">-->
                    <!--                                <option v-if="settingsLanguage == 'cn'" v-for="option in smartMaxTokensOptions_cn"-->
                    <!--                                        :value="option.state">{{ option.label }}-->
                    <!--                                </option>-->
                    <!--                                <option v-else v-for="option in smartMaxTokensOptions_en" :value="option.state">-->
                    <!--                                    {{ option.label }}-->
                    <!--                                </option>-->
                    <!--                            </select>-->
                    <!--                            <button @click="restoreDefault_APISettings"-->
                    <!--                                    class="btn minibtn minibtn-default bluebtn btnzoom-b"-->
                    <!--                                    :style="{ marginRight: settingsLanguage == 'cn' ? '-2px' : '-4px'}"-->
                    <!--                                    :title="settingsLanguage == 'cn' ? '恢复默认' : 'Restore Default'">-->
                    <!--                                {{ settingsLanguage == 'cn' ? '恢复默认' : 'Restore Default' }}-->
                    <!--                            </button>-->
                    <!--                        </div>-->

                    <!--                        <div class="panelrow" style="align-items: start; height:auto;">-->
                    <!--                            &lt;!&ndash;温度/随机性/创造力 temperature &ndash;&gt;-->
                    <!--                            <div class="control-container" style="width:108px;flex-shrink: 0;">-->
                    <!--                                <div>-->
                    <!--                                    <span>temperature: {{apitemperature}}</span>-->
                    <!--                                </div>-->
                    <!--                                <div class="control-bg">-->
                    <!--                                    <input @change="changeTemperature" type="range" class="slider"-->
                    <!--                                           v-model.number="apitemperature" min="0.0" max="2.0" step="0.1" value="0.7">-->
                    <!--                                </div>-->
                    <!--                            </div>-->
                    <!--                            &lt;!&ndash; 单次 最大令牌数量 max_tokens (4095 4096 4097 16384 16385  gpt-3.5 gpt-4 gpt3.5 gpt4) &ndash;&gt;-->
                    <!--                            <div class="control-container" style="flex: 1 1 0%;">-->
                    <!--                                <div>-->
                    <!--                                    <span>max_tokens(</span><span-->
                    <!--                                        style="margin-left: 0px;font-weight:500;">1~{{gptModelList.find(model => model.modelname === apiGPTModel).maxtokens-->
                    <!--                                    }}</span><span style="margin-left: 0px;">): {{apiMaxTokens}}</span>-->
                    <!--                                </div>-->
                    <!--                                <div class="control-bg">-->
                    <!--                                    <input @change="changeMaxTokens" type="range" :disabled="openSmartMaxTokens"-->
                    <!--                                           class="slider" v-model.number="apiMaxTokens" min="1"-->
                    <!--                                           :max="gptModelList.find(model => model.modelname === apiGPTModel).maxtokens"-->
                    <!--                                           step="1" value="2048">-->
                    <!--                                </div>-->
                    <!--                            </div>-->
                    <!--                        </div>-->

                    <!--                        <div class="panelrow" style="align-items: start; height:auto;">-->
                    <!--                            &lt;!&ndash; GPT模型 GPT Model &ndash;&gt;-->
                    <!--                            <div class="control-container" style="flex-shrink: 0;"-->
                    <!--                                 :style="{ width: apiGPTModel === 'gpt-3.5-turbo-16k-0613' ? '178px' : '156px'}">-->
                    <!--                                <div>-->
                    <!--                                    <span>model: {{apiGPTModel}}</span>-->
                    <!--                                </div>-->
                    <!--                                <select @change="changeGPTModel" class="dropdown" v-model="apiGPTModel"-->
                    <!--                                        style="width: 100%;height: 24px;">-->
                    <!--                                    <option v-for="model in gptModelList" :value="model.modelname">-->
                    <!--                                        {{ model.alias ? model.alias : model.modelname }}-->
                    <!--                                    </option>-->
                    <!--                                    <option disabled value="">-->
                    <!--                                        {{ settingsLanguage == 'cn' ? ' - 暂时只支持GPT3.5' : ' - Only supports GPT3.5 for now'-->
                    <!--                                        }}-->
                    <!--                                    </option>-->
                    <!--                                    <option disabled value="">-->
                    <!--                                        {{ settingsLanguage == 'cn' ? ' - 带有“16k”后缀的模型支持 16384 个 token' : ' - Models with the "16k" suffix support 16384 tokens'-->
                    <!--                                        }}-->
                    <!--                                    </option>-->
                    <!--                                </select>-->
                    <!--                            </div>-->

                    <!--                            &lt;!&ndash; 单次最大令牌数量（精细调节 1～300） max_tokens（Fine 1～300）  &ndash;&gt;-->
                    <!--                            <div class="control-container" style="flex: 1 1 0%;">-->
                    <!--                                <div>-->
                    <!--                                    <span :title="settingsLanguage == 'cn' ? 'max_tokens (1~300)' : 'max_tokens (1~300)'">{{ settingsLanguage == 'cn' ? 'max_tokens(微调)' : 'max_tokens(Fine)'-->
                    <!--                                        }}</span>-->
                    <!--                                </div>-->
                    <!--                                <div class="control-bg">-->
                    <!--                                    <input @change="changeMaxTokens" type="range" :disabled="openSmartMaxTokens"-->
                    <!--                                           class="slider" v-model.number="apiMaxTokens" min="1" max="300" step="1"-->
                    <!--                                           value="2048">-->
                    <!--                                </div>-->
                    <!--                            </div>-->

                    <!--                            &lt;!&ndash; 显示专业设置 Show more settings (Professional API Settings) &ndash;&gt;-->
                    <!--                            <div class="control-container">-->
                    <!--                                <div>-->
                    <!--                                    <span>{{ settingsLanguage == 'cn' ? ' 更多' : ' More' }}</span>-->
                    <!--                                </div>-->
                    <!--                                <button @click="showProConfig" class="btn bluebtn btnzoom-c"-->
                    <!--                                        :class="{ 'orangereddropdown': ( inputApiURL.trim() !== 'https://api.openai.com/v1/chat/completions' ) }"-->
                    <!--                                        style="margin: 0;padding: 0 4px;"-->
                    <!--                                        :title="settingsLanguage == 'cn' ? '显示更多专业设置' : 'Show more Pro settings'">-->
                    <!--                                    <div>-->
                    <!--                                        PRO-->
                    <!--                                    </div>-->
                    <!--                                </button>-->
                    <!--                            </div>-->
                    <!--                        </div>-->
                    <!--                    </div>-->

                    <!-- panelgroup: API专业设置  Professional API Settings -->
                    <!--                    <div class="panelgroup panelgroup-shadow" :style="{ display: isShowProConfig ? 'block' : 'none'}">-->
                    <!--                        <div class="panelgroup-head">-->
                    <!--                            <span>{{ settingsLanguage == 'cn' ? 'API专业设置' : 'Professional API Settings' }}</span>-->
                    <!--                            <button @click="showMiniHelp('A-ProfessionalAPISettings')"-->
                    <!--                                    class="btn minibtn bluebtn btnzoom-b" style="margin-right: auto;"-->
                    <!--                                    :title="settingsLanguage == 'cn' ? '功能说明' : 'Help'">?-->
                    <!--                            </button>-->
                    <!--                            <button @click="restoreDefault_ProfessionalAPISettings"-->
                    <!--                                    class="btn minibtn minibtn-default bluebtn btnzoom-b"-->
                    <!--                                    :style="{ marginRight: settingsLanguage == 'cn' ? '-2px' : '-4px'}"-->
                    <!--                                    :title="settingsLanguage == 'cn' ? '恢复默认' : 'Restore Default'">-->
                    <!--                                {{ settingsLanguage == 'cn' ? '恢复默认' : 'Restore Default' }}-->
                    <!--                            </button>-->
                    <!--                        </div>-->

                    <!--                        &lt;!&ndash; 接口网址 API URL / API Endpoint  &ndash;&gt;-->
                    <!--                        <div class="panelrow" style="align-items: start; height:auto;">-->
                    <!--						<span style="line-height: 25px;font-size: 12px;flex-shrink: 0;"-->
                    <!--                              :title="settingsLanguage == 'cn' ? 'API URL (API Endpoint)' : 'API Endpoint'">-->
                    <!--							{{ settingsLanguage == 'cn' ? '接口网址 :' : 'API URL :' }}-->
                    <!--						</span>-->
                    <!--                            <input type="text" @input="inputApiURLChange" v-model="inputApiURL" class="dh-input"-->
                    <!--                                   style="border-radius:6px 0px 0px 6px;"-->
                    <!--                                   :placeholder="settingsLanguage == 'cn' ? '清空后保存 可重置为OpenAI官方接口' : 'Clear and save, can be reset to OpenAI API endpoint.' "/>-->
                    <!--                            <button @click="saveApiURL" :disabled="btnDisabledState_SaveApiURL" class="btn"-->
                    <!--                                    style="border-radius: 0px 6px 6px 0px;margin-left:-3px;">-->
                    <!--                                {{ settingsLanguage == 'cn' ? '保存' : 'Save' }}-->
                    <!--                            </button>-->
                    <!--                            <select @change="updateInputApiURL" :disabled="btnDisabledState_ApiURL" class="dropdown"-->
                    <!--                                    v-model="selectedApiURL"-->
                    <!--                                    :class="{ 'orangereddropdown': ( inputApiURL.trim() !== 'https://api.openai.com/v1/chat/completions' ) }"-->
                    <!--                                    :style="{ width: selectedApiURL === '' ? '31px' : (  settingsLanguage == 'cn' ? (selectedApiURL === apiURLOptions_en[0]['url'] ? '46px' : '57px') : (selectedApiURL === apiURLOptions_en[0]['url'] ? '63px' : '71px')  )  }"-->
                    <!--                                    :title="settingsLanguage == 'cn' ? '接口URL选项 (可选)' : 'Endpoint Options (Optional)'">-->
                    <!--                                <option disabled value="">-->
                    <!--                                    ▼&nbsp;{{ settingsLanguage == 'cn' ? '预设选项：GPT官方接口 和 第三方接口' : 'Pre-set options: OpenAI API and Third-party API '-->
                    <!--                                    }}-->
                    <!--                                </option>-->
                    <!--                                <option v-if="settingsLanguage == 'cn'" v-for="option in apiURLOptions_cn"-->
                    <!--                                        :value="option.url">{{ option.label }}-->
                    <!--                                </option>-->
                    <!--                                <option v-else v-for="option in apiURLOptions_en" :value="option.url">{{ option.label-->
                    <!--                                    }}-->
                    <!--                                </option>-->
                    <!--                                <option disabled value="">-->
                    <!--                                    {{ settingsLanguage == 'cn' ? ' - 第三方接口来源网络，无法保证隐私安全和实效性' : ' - Third-party APIs can be unsafe or unreliable'-->
                    <!--                                    }}-->
                    <!--                                </option>-->
                    <!--                            </select>-->
                    <!--                        </div>-->

                    <!--                        <div class="panelrow" style="align-items: start; height:auto;">-->
                    <!--                            &lt;!&ndash; top_p &ndash;&gt;-->
                    <!--                            <div class="control-container" style="flex: 1 1 0%;">-->
                    <!--                                <div>-->
                    <!--                                    <span style="font-size: 11px;">top_p: {{apiTopP}}</span>-->
                    <!--                                </div>-->
                    <!--                                <div class="control-bg">-->
                    <!--                                    <input @change="changeTopP" type="range" class="slider" v-model.number="apiTopP"-->
                    <!--                                           min="0.0" max="1" step="0.1" value="1">-->
                    <!--                                </div>-->
                    <!--                            </div>-->

                    <!--                            &lt;!&ndash; presence_penalty &ndash;&gt;-->
                    <!--                            <div class="control-container" style="width: 48%;flex: 1 1 15%;">-->
                    <!--                                <div>-->
                    <!--                                    <span style="font-size: 11px;">presence_penalty: {{apiPresencePenalty}}</span>-->
                    <!--                                </div>-->
                    <!--                                <div class="control-bg">-->
                    <!--                                    <input @change="changePresencePenalty" type="range" class="slider"-->
                    <!--                                           v-model.number="apiPresencePenalty" min="-2.0" max="2.0" step="0.1"-->
                    <!--                                           value="0">-->
                    <!--                                </div>-->
                    <!--                            </div>-->

                    <!--                            &lt;!&ndash; frequency_penalty &ndash;&gt;-->
                    <!--                            <div class="control-container" style="width: 48%;flex: 1 1 15%;">-->
                    <!--                                <div>-->
                    <!--                                    <span style="font-size: 11px;">frequency_penalty: {{apiFrequencyPenalty}}</span>-->
                    <!--                                </div>-->
                    <!--                                <div class="control-bg">-->
                    <!--                                    <input @change="changeFrequencyPenalty" type="range" class="slider"-->
                    <!--                                           v-model.number="apiFrequencyPenalty" min="-2.0" max="2.0" step="0.1"-->
                    <!--                                           value="0">-->
                    <!--                                </div>-->
                    <!--                            </div>-->
                    <!--                        </div>-->
                    <!--                    </div>-->

                    <!-- panelgroup: 常规设置 General Settings -->
                    <div class="panelgroup panelgroup-shadow">
                        <div class="panelgroup-head">
                            <span>{{ settingsLanguage == 'cn' ? '常规设置' : 'General Settings' }}</span>
                            <button @click="showMiniHelp('A-GeneralSettings')" class="btn minibtn bluebtn btnzoom-b"
                                    style="margin-right: auto;" :title="settingsLanguage == 'cn' ? '功能说明' : 'Help'">
                                ?
                            </button>
                            <div style="margin-right: -7px;">
                                <svg width="22" height="22"
                                     :fill="(pageScaling === '0.8' || pageScaling === '0.80') ? '#25ad24' : '#f44400'"
                                     viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"
                                     xmlns:svg="http://www.w3.org/2000/svg" xmlns:se="http://svg-edit.googlecode.com">
                                    <path d="m22,29l-12,0a3,3 0 0 1 -3,-3l0,-20a3,3 0 0 1 3,-3l12,0a3,3 0 0 1 3,3l0,20a3,3 0 0 1 -3,3zm-12,-24a1,1 0 0 0 -1,1l0,20a1,1 0 0 0 1,1l12,0a1,1 0 0 0 1,-1l0,-20a1,1 0 0 0 -1,-1l-12,0z"
                                          id="svg_1"/>
                                    <path d="m18,7l-4,0a2,2 0 0 1 -2,-2l0,-1a1,1 0 0 1 1,-1l6,0a1,1 0 0 1 1,1l0,1a2,2 0 0 1 -2,2zm-4,-2l0,0z"/>
                                </svg>
                            </div>
                            <select @change="changePageScaling(true)" class="dropdown minibtn minibtn-default btnzoom-b"
                                    v-model="selectedPageScaling"
                                    :class="{ 'orangereddropdown': !(pageScaling === '0.8' || pageScaling === '0.80') }"
                                    :style="{ marginRight: settingsLanguage == 'cn' ? '-4px' : '-7px', width: settingsLanguage == 'cn' ? '64px' : '90px' }"
                                    :title="settingsLanguage == 'cn' ? '更改页面缩放比例 (移动端)' : 'Change Page Scaling (Mobile)'">
                                <option disabled value="">{{ settingsLanguage == 'cn' ? '页面缩放' : 'Page Scaling' }}
                                </option>
                                <option disabled value="help1">
                                    {{ settingsLanguage == 'cn' ? ' - 当前缩放为 ' + (parseFloat(this.pageScaling) * 100).toFixed(0) + '%，默认的缩放设置是 80%' : ' - Current scaling is ' + (parseFloat(this.pageScaling) * 100).toFixed(0) + '%, and default scaling is 80%'
                                    }}
                                </option>
                                <option disabled value="help2">
                                    {{ settingsLanguage == 'cn' ? ' - 仅支持移动设备，电脑端不会进行缩放' : ' - Only mobile devices are supported, and there will be no scaling on desktop'
                                    }}
                                </option>
                                <option v-for="option in selectedPageScalingOptions" :value="option">{{ option }}
                                </option>
                            </select>
                            <button @click="restoreDefault_GeneralSettings"
                                    class="btn minibtn minibtn-default bluebtn btnzoom-b"
                                    :style="{ marginRight: settingsLanguage == 'cn' ? '-2px' : '-4px'}"
                                    :title="settingsLanguage == 'cn' ? '恢复默认' : 'Restore Default'">
                                {{ settingsLanguage == 'cn' ? '恢复默认' : 'Restore Default' }}
                            </button>
                        </div>

                        <div class="panelrow"
                             style="align-items: start; height:auto;flex-wrap: wrap;justify-content: stretch;">
                            <!-- 语言切换 中/英 Language -->
                            <div class="control-container"
                                 style="min-width: 80px;max-width: 180px;flex: 1 1 0%;margin: 0px 3px;">
                                <div>
                                    <span>{{ settingsLanguage == 'cn' ? 'Language &#127464;&#127475;' : '语言 &#x1F1EC;&#x1F1E7;'
                                        }}</span>
                                </div>
                                <select @change="changeLanguage" :disabled="btnDisabledState_Language" class="dropdown"
                                        v-model="settingsLanguage" style="width: 100%;height: 24px;">
                                    <option v-for="option in settingsLanguageOptions" :value="option.lang">
                                        {{ option.label }}
                                    </option>
                                </select>
                            </div>
                            <!-- 字体大小 Font Size -->
                            <div class="control-container"
                                 style="min-width: 65px;max-width: 165px;flex: 1 1 0%;margin: 0px 3px;">
                                <div>
                                    <span>{{ settingsLanguage == 'cn' ? '字体大小' : 'Font Size' }}</span>
                                </div>
                                <select @change="changeStyle_FontSize" class="dropdown" v-model="settingsFontSize"
                                        style="width: 100%;height: 24px;">
                                    <option disabled value="">
                                        {{ settingsLanguage == 'cn' ? ' - 避免在文本过多的对话中进行更改，以免导致网页崩溃' : " - To avoid crashes, don't make changes in chats with excessive content"
                                        }}
                                    </option>
                                    <option v-for="option in settingsFontSizeOptions" :value="option.fontsize">
                                        {{ option.label }}
                                    </option>
                                </select>
                            </div>
                            <!-- 显示时间(微信样式)  Display time (WeChat style) -->
                            <div class="control-container"
                                 style="min-width: 125px;max-width: 225px;flex: 1 1 0%;margin: 0px 3px;">
                                <div>
                                    <span>{{ settingsLanguage == 'cn' ? '仿微信时间' : 'Time (WeChat style)' }}</span>
                                </div>
                                <select @change="changeStyle_Time_WechatStyle" class="dropdown"
                                        v-model="settingsTime_WechatStyle" style="width: 100%;height: 24px;">
                                    <option v-if="settingsLanguage == 'cn'"
                                            v-for="option in settingsTimeWechatStyleOptions_cn"
                                            :value="option.displaytime">{{ option.label }}
                                    </option>
                                    <option v-else v-for="option in settingsTimeWechatStyleOptions_en"
                                            :value="option.displaytime">{{ option.label }}
                                    </option>
                                </select>
                            </div>
                            <!-- 显示消息时间  Display message time.  -->
                            <div class="control-container"
                                 style="min-width: 105px;max-width: 205px;flex: 1 1 0%;margin: 0px 3px;">
                                <div>
                                    <span>{{ settingsLanguage == 'cn' ? '昵称' : 'nickName' }}</span>
                                </div>
                                <select @change="changeStyle_Time" class="dropdown" v-model="settingsTime_Message"
                                        style="width: 100%;height: 24px;">
                                    <option v-if="settingsLanguage == 'cn'"
                                            v-for="option in settingsTimeMessageOptions_cn" :value="option.displaytime">
                                        {{ option.label }}
                                    </option>
                                    <option v-else v-for="option in settingsTimeMessageOptions_en"
                                            :value="option.displaytime">{{ option.label }}
                                    </option>
                                </select>
                            </div>
                            <!-- 显示分割线 记忆分割线/上下文分割线 Display divider line -->
                            <div class="control-container"
                                 style="min-width: 125px;max-width: 225px;flex: 1 1 0%;margin: 0px 3px;">
                                <div>
                                    <span>{{ settingsLanguage == 'cn' ? '记忆分割线' : 'Context Divider Line' }}</span>
                                </div>
                                <select @change="changeStyle_DividerLine" class="dropdown" v-model="settingsDividerLine"
                                        style="width: 100%;height: 24px;">
                                    <option v-if="settingsLanguage == 'cn'"
                                            v-for="option in settingsDividerLineOptions_cn"
                                            :value="option.displaydividerline">{{ option.label }}
                                    </option>
                                    <option v-else v-for="option in settingsDividerLineOptions_en"
                                            :value="option.displaydividerline">{{ option.label }}
                                    </option>
                                </select>
                            </div>


                            <!-- 记忆模式 / 上下文模式  Contextual Mode -->
                            <div class="control-container"
                                 style="min-width: 110px;max-width: 210px;flex: 1 1 0%;margin: 0px 3px;">
                                <div>
                                    <span>{{ settingsLanguage == 'cn' ? '记忆模式' : 'Contextual Mode' }}</span>
                                </div>
                                <select @change="changeContextualMode" :disabled="btnDisabledState_ContextualMode"
                                        class="dropdown" v-model="settingsContextualMode"
                                        style="width: 100%;height: 24px;">
                                    <option v-if="settingsLanguage == 'cn'"
                                            v-for="option in settingsContextualModeOptions_cn" :value="option.mode">
                                        {{ option.label }}
                                    </option>
                                    <option v-else v-for="option in settingsContextualModeOptions_en"
                                            :value="option.mode">{{ option.label }}
                                    </option>
                                    <option disabled value="">更多:待开发 More:Under development</option>
                                </select>
                            </div>

                            <!--                            &lt;!&ndash; 验证API Key 快捷键 Check API Key Shortcut  &ndash;&gt;-->
                            <!--                            <div class="control-container"-->
                            <!--                                 style="min-width: 150px;max-width: 260px;flex: 1 1 0%;margin: 0px 3px;">-->
                            <!--                                <div>-->
                            <!--                                    <span>{{ settingsLanguage == 'cn' ? '[验证API Key]快捷键' : '[Check API Key] Shortcut'-->
                            <!--                                        }}</span>-->
                            <!--                                </div>-->
                            <!--                                <select class="dropdown" v-model="settingsShortcut_CheckApiKey"-->
                            <!--                                        style="width: 100%;height: 24px;">-->
                            <!--                                    <option v-if="settingsLanguage == 'cn'"-->
                            <!--                                            v-for="option in settingsShortcutCheckApiKeyOptions_cn"-->
                            <!--                                            :value="option.shortcut">{{ option.label }}-->
                            <!--                                    </option>-->
                            <!--                                    <option v-else v-for="option in settingsShortcutCheckApiKeyOptions_en"-->
                            <!--                                            :value="option.shortcut">{{ option.label }}-->
                            <!--                                    </option>-->
                            <!--                                </select>-->
                            <!--                            </div>-->

                            <!--                            &lt;!&ndash; 清空快捷键 Clear Context Shortcut&ndash;&gt;-->
                            <!--                            <div class="control-container"-->
                            <!--                                 style="min-width: 140px;max-width: 250px;flex: 1 1 0%;margin: 0px 3px;">-->
                            <!--                                <div>-->
                            <!--                                    <span>{{ settingsLanguage == 'cn' ? '[清空]快捷键' : '[Clear Context] Shortcut'-->
                            <!--                                        }}</span>-->
                            <!--                                </div>-->
                            <!--                                <select class="dropdown" v-model="settingsShortcut_Clear"-->
                            <!--                                        style="width: 100%;height: 24px;">-->
                            <!--                                    <option v-if="settingsLanguage == 'cn'"-->
                            <!--                                            v-for="option in settingsShortcutClearOptions_cn" :value="option.shortcut">-->
                            <!--                                        {{ option.label }}-->
                            <!--                                    </option>-->
                            <!--                                    <option v-else v-for="option in settingsShortcutClearOptions_en"-->
                            <!--                                            :value="option.shortcut">{{ option.label }}-->
                            <!--                                    </option>-->
                            <!--                                </select>-->
                            <!--                            </div>-->

                            <!--                            &lt;!&ndash; 撤销快捷键 Undo Shortcut &ndash;&gt;-->
                            <!--                            <div class="control-container"-->
                            <!--                                 style="min-width: 95px;max-width: 195px;flex: 1 1 0%;margin: 0px 3px;">-->
                            <!--                                <div>-->
                            <!--                                    <span>{{ settingsLanguage == 'cn' ? '[撤销]快捷键' : '[Undo] Shortcut' }}</span>-->
                            <!--                                </div>-->
                            <!--                                <select class="dropdown" v-model="settingsShortcut_Undo"-->
                            <!--                                        style="width: 100%;height: 24px;">-->
                            <!--                                    <option v-if="settingsLanguage == 'cn'"-->
                            <!--                                            v-for="option in settingsShortcutUndoOptions_cn" :value="option.shortcut">-->
                            <!--                                        {{ option.label }}-->
                            <!--                                    </option>-->
                            <!--                                    <option v-else v-for="option in settingsShortcutUndoOptions_en"-->
                            <!--                                            :value="option.shortcut">{{ option.label }}-->
                            <!--                                    </option>-->
                            <!--                                </select>-->
                            <!--                            </div>-->

                            <!--                            &lt;!&ndash; 重问快捷键 Retry Shortcut &ndash;&gt;-->
                            <!--                            <div class="control-container"-->
                            <!--                                 style="min-width: 95px;max-width: 195px;flex: 1 1 0%;margin: 0px 3px;">-->
                            <!--                                <div>-->
                            <!--                                    <span>{{ settingsLanguage == 'cn' ? '[重问]快捷键' : '[Retry] Shortcut' }}</span>-->
                            <!--                                </div>-->
                            <!--                                <select class="dropdown" v-model="settingsShortcut_Retry"-->
                            <!--                                        style="width: 100%;height: 24px;">-->
                            <!--                                    <option v-if="settingsLanguage == 'cn'"-->
                            <!--                                            v-for="option in settingsShortcutRetryOptions_cn" :value="option.shortcut">-->
                            <!--                                        {{ option.label }}-->
                            <!--                                    </option>-->
                            <!--                                    <option v-else v-for="option in settingsShortcutRetryOptions_en"-->
                            <!--                                            :value="option.shortcut">{{ option.label }}-->
                            <!--                                    </option>-->
                            <!--                                </select>-->
                            <!--                            </div>-->
                        </div>

                        <!-- 用户头像 user Avatar  -->
                        <div class="panelrow" style="height:36px;">
                            <select class="dropdown" v-model="selectedImageUrl" @change="updateSelectedImage('user')"
                                    :style="{ width: settingsLanguage == 'cn' ? '72px' : '87px' }"
                                    style="flex-shrink: 0;height: 24px;margin-right: 4px;margin-left: 3px;"
                                    :title="settingsLanguage == 'cn' ? '用户头像选项 (可选)' : 'User Avatar Options (Optional)'">
                                <option value="">{{ settingsLanguage == 'cn' ? '用户头像' : 'User Avatar' }}</option>
                                <option v-if="!isSimpleMode" disabled value="help1">
                                    {{ settingsLanguage == 'cn' ? ' - 支持多种方式更改头像，点击面板[?]了解更多' : ' - Offering multiple options to change avatars. Click the panel [?] to learn more'
                                    }}
                                </option>
                                <option value="uploadimage">
                                    {{ settingsLanguage == 'cn' ? ' - 点击右侧头像 可上传图片' : ' - Click the Avatar on the right to upload an image'
                                    }}
                                </option>
                                <option :value="userAvatarUp_blobUrl">
                                    {{ settingsLanguage == 'cn' ? (userAvatarUp_blobUrl == '' ? '1) 最近上传的用户头像 (您还没上传小于350KB的头像)' : '1) 最近上传的用户头像 (可用)') : (userAvatarUp_blobUrl == '' ? '1) Uploaded User Avatar (No uploaded avatar within 350KB)' : '1) Uploaded User Avatar (Available)')
                                    }}
                                </option>
                                <option v-if="settingsLanguage == 'cn'" v-for="image in userpresetImages_cn"
                                        :value="image.url">{{ image.name }}
                                </option>
                                <option v-else v-for="image in userpresetImages_en" :value="image.url">{{ image.name
                                    }}
                                </option>
                            </select>
                            <span style="line-height: 25px; font-size: 12px; flex-shrink: 0;"
                                  :title="settingsLanguage == 'cn' ? '或手动输入网络图片网址/本地图片路径' : 'or Manually Enter Image URL / Local Image Path'">{{ settingsLanguage == 'cn' ? '或 URL:' : 'or URL'
                                }}</span>
                            <input type="text" v-model="inputUserImageUrl" @input="inputImageUrlChange('user')"
                                   class="dh-input" style="flex: 1 1 0%;border-radius:6px 0px 0px 6px;"
                                   :placeholder="settingsLanguage == 'cn' ? '输入图片的网址（本地运行.html网页时支持电脑中图片的本地路径）' : 'Image URL. (Local image paths are supported when running *.html from PC/Mac.)' ">
                            <button @click="updateAvatar('user')" :disabled="btnDisabledState_updateUserImageUrl"
                                    class="btn" :style="{ width: settingsLanguage == 'cn' ? '68px' : '85px' }"
                                    style="flex-shrink: 0;border-radius: 0px 6px 6px 0px;margin-left:-3px;margin-right: 4px;"
                                    :title="settingsLanguage == 'cn' ? '可选: 在修改图片网址后 点击更换' : 'Optional: Click Update after modifying the image URL'">
                                {{ settingsLanguage == 'cn' ? '更换 >>' : 'Update >>' }}
                            </button>
                            <div id="div-upload-userAvataror"
                                 class="chat-img chat-img-bg chat-img-border update-img-size btnzoom-a"
                                 :title="settingsLanguage == 'cn' ? '可选: 点击上传我的头像' : 'Optional: Click to upload my avatar'">
                                <img :src="userAvatarURL">
                            </div>
                        </div>
                        <!-- GPT头像 gpt Avatar  -->
                        <div class="panelrow" style="height:35px;">
                            <select class="dropdown" v-model="selectedImageUrl" @change="updateSelectedImage('gpt')"
                                    :style="{ width: settingsLanguage == 'cn' ? '72px' : '87px' }"
                                    style="flex-shrink: 0;height: 24px;margin-right: 4px;margin-left: 3px;"
                                    :title="settingsLanguage == 'cn' ? 'GPT头像选项 (可选)' : 'GPT Avatar Options (Optional)'">
                                <option value="">{{ settingsLanguage == 'cn' ? 'GPT头像' : 'GPT Avatar' }}</option>
                                <option v-if="!isSimpleMode" disabled value="help1">
                                    {{ settingsLanguage == 'cn' ? ' - 支持多种方式更改头像，点击面板[?]了解更多' : ' - Offering multiple options to change avatars. Click the panel [?] to learn more'
                                    }}
                                </option>
                                <option value="uploadimage">
                                    {{ settingsLanguage == 'cn' ? ' - 点击右侧头像 可上传图片' : ' - Click the Avatar on the right to upload an image'
                                    }}
                                </option>
                                <option v-if="settingsLanguage == 'cn'" v-for="image in gptpresetImages_cn"
                                        :value="image.url">{{ image.name }}
                                </option>
                                <option v-else v-for="image in gptpresetImages_en" :value="image.url">{{ image.name }}
                                </option>
                            </select>
                            <span style="line-height: 25px; font-size: 12px; flex-shrink: 0;"
                                  :title="settingsLanguage == 'cn' ? '或手动输入网络图片网址/本地图片路径' : 'or Manually Enter Image URL / Local Image Path'">{{ settingsLanguage == 'cn' ? '或 URL:' : 'or URL'
                                }}</span>
                            <input type="text" v-model="inputGPTImageUrl" @input="inputImageUrlChange('gpt')"
                                   class="dh-input" style="flex: 1 1 0%;border-radius:6px 0px 0px 6px;"
                                   :placeholder="settingsLanguage == 'cn' ? '输入图片的网址（本地运行.html网页时支持电脑中图片的本地路径）' : 'Image URL. (Local image paths are supported when running *.html from PC/Mac.)' ">
                            <button @click="updateAvatar('gpt')" :disabled="btnDisabledState_updateGPTImageUrl"
                                    class="btn" :style="{ width: settingsLanguage == 'cn' ? '68px' : '85px' }"
                                    style="flex-shrink: 0;border-radius: 0px 6px 6px 0px;margin-left:-3px;margin-right: 4px;"
                                    :title="settingsLanguage == 'cn' ? '可选: 在修改图片网址后 点击更换' : 'Optional: Click Update after modifying the image URL'">
                                {{ settingsLanguage == 'cn' ? '更换 >>' : 'Update >>' }}
                            </button>
                            <div id="div-upload-gptAvataror"
                                 class="chat-img chat-img-bg chat-img-border update-img-size btnzoom-a"
                                 :style="{ display: isgptGreenSvgShow ? 'none' : 'flex' }"
                                 :title="settingsLanguage == 'cn' ? '可选: 点击上传GPT头像' : 'Optional: Click to upload GPT avatar'">
                                <img :src="gptAvatarURL">
                            </div>
                            <div id="div-upload-gptAvataror-SVG"
                                 class="chat-img chat-img-border chat-green-svg update-img-size btnzoom-a"
                                 :style="{ display: isgptGreenSvgShow  ? 'flex' : 'none' }"
                                 :title="settingsLanguage == 'cn' ? '可选: 点击上传GPT头像' : 'Optional: Click to upload GPT avatar'">
                                <svg width="27" height="27" viewBox="0 0 41 41" fill="none"
                                     xmlns="http://www.w3.org/2000/svg" stroke-width="1.5" role="img">
                                    <path d="M37.5324 16.8707C37.9808 15.5241 38.1363 14.0974 37.9886 12.6859C37.8409 11.2744 37.3934 9.91076 36.676 8.68622C35.6126 6.83404 33.9882 5.3676 32.0373 4.4985C30.0864 3.62941 27.9098 3.40259 25.8215 3.85078C24.8796 2.7893 23.7219 1.94125 22.4257 1.36341C21.1295 0.785575 19.7249 0.491269 18.3058 0.500197C16.1708 0.495044 14.0893 1.16803 12.3614 2.42214C10.6335 3.67624 9.34853 5.44666 8.6917 7.47815C7.30085 7.76286 5.98686 8.3414 4.8377 9.17505C3.68854 10.0087 2.73073 11.0782 2.02839 12.312C0.956464 14.1591 0.498905 16.2988 0.721698 18.4228C0.944492 20.5467 1.83612 22.5449 3.268 24.1293C2.81966 25.4759 2.66413 26.9026 2.81182 28.3141C2.95951 29.7256 3.40701 31.0892 4.12437 32.3138C5.18791 34.1659 6.8123 35.6322 8.76321 36.5013C10.7141 37.3704 12.8907 37.5973 14.9789 37.1492C15.9208 38.2107 17.0786 39.0587 18.3747 39.6366C19.6709 40.2144 21.0755 40.5087 22.4946 40.4998C24.6307 40.5054 26.7133 39.8321 28.4418 38.5772C30.1704 37.3223 31.4556 35.5506 32.1119 33.5179C33.5027 33.2332 34.8167 32.6547 35.9659 31.821C37.115 30.9874 38.0728 29.9178 38.7752 28.684C39.8458 26.8371 40.3023 24.6979 40.0789 22.5748C39.8556 20.4517 38.9639 18.4544 37.5324 16.8707ZM22.4978 37.8849C20.7443 37.8874 19.0459 37.2733 17.6994 36.1501C17.7601 36.117 17.8666 36.0586 17.936 36.0161L25.9004 31.4156C26.1003 31.3019 26.2663 31.137 26.3813 30.9378C26.4964 30.7386 26.5563 30.5124 26.5549 30.2825V19.0542L29.9213 20.998C29.9389 21.0068 29.9541 21.0198 29.9656 21.0359C29.977 21.052 29.9842 21.0707 29.9867 21.0902V30.3889C29.9842 32.375 29.1946 34.2791 27.7909 35.6841C26.3872 37.0892 24.4838 37.8806 22.4978 37.8849ZM6.39227 31.0064C5.51397 29.4888 5.19742 27.7107 5.49804 25.9832C5.55718 26.0187 5.66048 26.0818 5.73461 26.1244L13.699 30.7248C13.8975 30.8408 14.1233 30.902 14.3532 30.902C14.583 30.902 14.8088 30.8408 15.0073 30.7248L24.731 25.1103V28.9979C24.7321 29.0177 24.7283 29.0376 24.7199 29.0556C24.7115 29.0736 24.6988 29.0893 24.6829 29.1012L16.6317 33.7497C14.9096 34.7416 12.8643 35.0097 10.9447 34.4954C9.02506 33.9811 7.38785 32.7263 6.39227 31.0064ZM4.29707 13.6194C5.17156 12.0998 6.55279 10.9364 8.19885 10.3327C8.19885 10.4013 8.19491 10.5228 8.19491 10.6071V19.808C8.19351 20.0378 8.25334 20.2638 8.36823 20.4629C8.48312 20.6619 8.64893 20.8267 8.84863 20.9404L18.5723 26.5542L15.206 28.4979C15.1894 28.5089 15.1703 28.5155 15.1505 28.5173C15.1307 28.5191 15.1107 28.516 15.0924 28.5082L7.04046 23.8557C5.32135 22.8601 4.06716 21.2235 3.55289 19.3046C3.03862 17.3858 3.30624 15.3413 4.29707 13.6194ZM31.955 20.0556L22.2312 14.4411L25.5976 12.4981C25.6142 12.4872 25.6333 12.4805 25.6531 12.4787C25.6729 12.4769 25.6928 12.4801 25.7111 12.4879L33.7631 17.1364C34.9967 17.849 36.0017 18.8982 36.6606 20.1613C37.3194 21.4244 37.6047 22.849 37.4832 24.2684C37.3617 25.6878 36.8382 27.0432 35.9743 28.1759C35.1103 29.3086 33.9415 30.1717 32.6047 30.6641C32.6047 30.5947 32.6047 30.4733 32.6047 30.3889V21.188C32.6066 20.9586 32.5474 20.7328 32.4332 20.5338C32.319 20.3348 32.154 20.1698 31.955 20.0556ZM35.3055 15.0128C35.2464 14.9765 35.1431 14.9142 35.069 14.8717L27.1045 10.2712C26.906 10.1554 26.6803 10.0943 26.4504 10.0943C26.2206 10.0943 25.9948 10.1554 25.7963 10.2712L16.0726 15.8858V11.9982C16.0715 11.9783 16.0753 11.9585 16.0837 11.9405C16.0921 11.9225 16.1048 11.9068 16.1207 11.8949L24.1719 7.25025C25.4053 6.53903 26.8158 6.19376 28.2383 6.25482C29.6608 6.31589 31.0364 6.78077 32.2044 7.59508C33.3723 8.40939 34.2842 9.53945 34.8334 10.8531C35.3826 12.1667 35.5464 13.6095 35.3055 15.0128ZM14.2424 21.9419L10.8752 19.9981C10.8576 19.9893 10.8423 19.9763 10.8309 19.9602C10.8195 19.9441 10.8122 19.9254 10.8098 19.9058V10.6071C10.8107 9.18295 11.2173 7.78848 11.9819 6.58696C12.7466 5.38544 13.8377 4.42659 15.1275 3.82264C16.4173 3.21869 17.8524 2.99464 19.2649 3.1767C20.6775 3.35876 22.0089 3.93941 23.1034 4.85067C23.0427 4.88379 22.937 4.94215 22.8668 4.98473L14.9024 9.58517C14.7025 9.69878 14.5366 9.86356 14.4215 10.0626C14.3065 10.2616 14.2466 10.4877 14.2479 10.7175L14.2424 21.9419ZM16.071 17.9991L20.4018 15.4978L24.7325 17.9975V22.9985L20.4018 25.4983L16.071 22.9985V17.9991Z"
                                          fill="currentColor">
                                </svg>
                            </div>
                        </div>
                    </div>

                    <!-- panelgroup: 打开对话列表 open Chat List   打开提示词窗口 open Prompt Generator   下载聊天记录 Export chat history. -->
                    <div class="panelgroup panelgroup-shadow">
                        <div class="panelrow"
                             style="align-items: center; height:auto;flex-wrap: wrap;justify-content: stretch;">
                            <div class="control-container" style=" flex: 1 1 0%;"
                                 :style="{ minWidth: settingsLanguage == 'cn' ? '119px' : '129px',maxWidth: settingsLanguage == 'cn' ? '179px' : '199px' }">
                                <div>
                                    <span>{{ settingsLanguage == 'cn' ? '对话列表' : 'Chat List' }}</span>
                                    <button @click="showMiniHelp('C-ChatList')" class="btn minibtn bluebtn btnzoom-b"
                                            :title="settingsLanguage == 'cn' ? '功能说明' : 'Help'">?
                                    </button>
                                </div>
                                <button @click="clickOpenChatList" class="btn bluebtn"
                                        style="width:100%;margin: 2px 0px 4px 0px;justify-content: start;">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
                                         fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"
                                         stroke-linejoin="round">
                                        <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
                                    </svg>&nbsp;{{ settingsLanguage == 'cn' ? '打开对话列表' : 'Open Chat List' }}
                                </button>
                            </div>
                            <div class="control-container" style=" flex: 1 1 0%;"
                                 :style="{ minWidth: settingsLanguage == 'cn' ? '130px' : '179px',maxWidth: settingsLanguage == 'cn' ? '190px' : '239px' }">
                                <div>
                                    <span>{{ settingsLanguage == 'cn' ? '提示词窗口' : 'Prompt Generator' }}</span>
                                </div>
                                <button @click="clickOpenPromptGenerator" class="btn bluebtn"
                                        style="width:100%;margin: 2px 0px 4px 0px;justify-content: start;">
                                    <svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24"
                                         fill="currentColor" stroke="currentColor" stroke-width="0.6"
                                         stroke-linecap="round" stroke-linejoin="round">
                                        <path d="M19.7,23.5H4.3c-2.1,0-3.8-1.7-3.8-3.8V4.3c0-2.1,1.7-3.8,3.8-3.8h15.3c2.1,0,3.8,1.7,3.8,3.8v15.3    C23.5,21.8,21.8,23.5,19.7,23.5z M4.3,1.5c-1.6,0-2.8,1.3-2.8,2.8v15.3c0,1.6,1.3,2.8,2.8,2.8h15.3c1.6,0,2.8-1.3,2.8-2.8V4.3    c0-1.6-1.3-2.8-2.8-2.8H4.3z"/>
                                        <path d="M10.4,19.2H7.3c-0.3,0-0.5-0.2-0.5-0.5V5.3c0-0.3,0.2-0.5,0.5-0.5h9.3c0.3,0,0.5,0.2,0.5,0.5v8.5c0,0.3-0.2,0.5-0.5,0.5    h-5.7v4.4C10.9,19,10.7,19.2,10.4,19.2z M7.8,18.2h2.1v-4.4c0-0.3,0.2-0.5,0.5-0.5h5.7V5.8H7.8V18.2z M13.6,11.2h-3.2    c-0.3,0-0.5-0.2-0.5-0.5V8.4c0-0.3,0.2-0.5,0.5-0.5h3.2c0.3,0,0.5,0.2,0.5,0.5v2.3C14.1,11,13.9,11.2,13.6,11.2z M10.9,10.2h2.2    V8.9h-2.2V10.2z"/>
                                    </svg>&nbsp;{{ settingsLanguage == 'cn' ? '打开提示词窗口' : 'Open Prompt Generator'
                                    }}
                                </button>
                            </div>
                            <div class="control-container" style=" flex: 1 1 0%;"
                                 :style="{ minWidth: settingsLanguage == 'cn' ? '130px' : '179px' }">
                                <div>
                                    <span>{{ settingsLanguage == 'cn' ? '导出聊天记录/日志' : 'Export Chat History / Logs'
                                        }}</span>
                                    <button @click="showMiniHelp('A-ExportChatHistory')"
                                            class="btn minibtn bluebtn btnzoom-b" style="margin-right: auto;"
                                            :title="settingsLanguage == 'cn' ? '功能说明' : 'Help'">?
                                    </button>
                                </div>
                                <select @change="changeSelectedExportChatHistory" :disabled="btnDisabledState_Export"
                                        class="dropdown" v-model="selectedExportChatHistory"
                                        style="width: 100%; height: 25px; margin: 2px 0px 4px;">
                                    <option value="">
                                        {{ settingsLanguage == 'cn' ? '当前对话ID: #' + (this.chatid == null ? '' : (this.chatid.toString().length > 18 ? (this.chatid.toString().slice(0, 18) + " ...") : this.chatid.toString().slice(0, 18))) : 'Current Chat ID: #' + (this.chatid == null ? '' : (this.chatid.toString().length > 18 ? (this.chatid.toString().slice(0, 18) + " ...") : this.chatid.toString().slice(0, 18)))
                                        }}
                                    </option>
                                    <option disabled value="help1">
                                        {{ settingsLanguage == 'cn' ? ' - 导出当前对话的文本内容' : ' - Export the text content of the current chat'
                                        }}
                                    </option>
                                    <option v-if="settingsLanguage == 'cn'"
                                            v-for="option in selectedExportChatHistoryOptions_cn" :value="option.type">
                                        {{ option.label }}
                                    </option>
                                    <option v-else v-for="option in selectedExportChatHistoryOptions_en"
                                            :value="option.type">{{ option.label }}
                                    </option>
                                    <option v-if="!isSimpleMode" disabled value="help2">
                                        {{ settingsLanguage == 'cn' ? ' - 导出为纯文本，不可用于导入' : ' - Export to Plain Text, Not available for import'
                                        }}
                                    </option>
                                    <option v-if="!isSimpleMode" disabled value="help3">
                                        {{ settingsLanguage == 'cn' ? ' - 文本内容使用Markdown格式' : ' - Plain text is formatted using Markdown syntax'
                                        }}
                                    </option>
                                </select>
                            </div>
                        </div>
                    </div>

                    <!-- panelgroup: 数据管理与隐私 Data Privacy and Management. -->
                    <div class="panelgroup panelgroup-shadow">
                        <div class="panelgroup-head">
                            <span>{{ settingsLanguage == 'cn' ? '数据管理与隐私 ' : 'Data Privacy and Management '
                                }}</span>
                            <button @click="showMiniHelp('A-DataPrivacyAndManagement')"
                                    class="btn minibtn bluebtn btnzoom-b"
                                    :class="{ 'orangeredbtn': (isLocalStorageSupported && !isSimpleMode) ,'redbtn': !isLocalStorageSupported }"
                                    :style="{ marginRight: isLocalStorageSupported ? '2px' : '2px' }"
                                    :title="settingsLanguage == 'cn' ? '功能说明' : 'Help'">?
                            </button>
                            <span :style="{ color: isLocalStorageSupported ? '#f94000' : '#e63214'}"
                                  style="display:block; margin-right: auto;font-size: 12px;">{{ isLocalStorageSupported ? (isSimpleMode ? '' : (settingsLanguage == 'cn' ? '已启用自动保存，用户数据仅存储在浏览器的缓存中' : 'Autosave has been enabled. User data is stored solely in the browser’s local storage.')) : (settingsLanguage == 'cn' ? '自动保存不可用，当前浏览器或浏览器模式不支持"浏览器本地存储"' : 'Autosave is not available. LocalStorage is not supported in this browser or browser mode.')
                                }}</span>
                        </div>

                        <div class="panelrow"
                             style="align-items: start; height:auto;flex-wrap: wrap;justify-content: stretch;">
                            <select @change="changeSelectedDeleteLocalStorageData" class="dropdown"
                                    v-model="selectedDeleteLocalStorageData"
                                    style="width: auto;height: 24px; flex: 1 1 0%; margin: 4px 3px 1px 0px;"
                                    :style="{ minWidth: settingsLanguage == 'cn' ? '71px' : '85px' }">
                                <option value="">{{ settingsLanguage == 'cn' ? '删除数据' : 'Delete Data' }}</option>
                                <option v-if="!isSimpleMode" disabled value="help">
                                    {{ settingsLanguage == 'cn' ? ' - 谨慎操作，数据删除不可逆' : ' - The deletion of data is irreversible'
                                    }}
                                </option>
                                <option v-if="settingsLanguage == 'cn'"
                                        v-for="option in selectedDeleteLocalStorageDataOptions_cn" :value="option.type">
                                    {{ option.label }}
                                </option>
                                <option v-else v-for="option in selectedDeleteLocalStorageDataOptions_en"
                                        :value="option.type">{{ option.label }}
                                </option>
                            </select>
                            <select @change="changeSelectedExportLocalStorageData" :disabled="!isLocalStorageSupported"
                                    class="dropdown" v-model="selectedExportLocalStorageData"
                                    style="width: auto;height: 24px; flex: 1 1 0%; margin: 4px 3px 1px 3px;"
                                    :style="{ minWidth: settingsLanguage == 'cn' ? '86px' : '95px' }">
                                <option value="">{{ settingsLanguage == 'cn' ? '导出 (.json)' : 'Export (.json)' }}
                                </option>
                                <option v-if="!isSimpleMode" disabled value="help1">
                                    {{ settingsLanguage == 'cn' ? ' - 导出的JSON文件，可再次导入到LinGPT' : ' - Export as JSON File for Re-import into LinGPT'
                                    }}
                                </option>
                                <option v-if="!isSimpleMode" disabled value="help2">
                                    {{ settingsLanguage == 'cn' ? ' - 用途：迁移数据、分享给他人、备份数据' : ' - Use: Data Transfer, Share with Others, Backup'
                                    }}
                                </option>
                                <option v-if="settingsLanguage == 'cn'"
                                        v-for="option in selectedExportLocalStorageDataOptions_cn" :value="option.type">
                                    {{ option.label }}
                                </option>
                                <option v-else v-for="option in selectedExportLocalStorageDataOptions_en"
                                        :value="option.type">{{ option.label }}
                                </option>
                                <option v-if="!isSimpleMode" disabled value="help3">
                                    {{ settingsLanguage == 'cn' ? ' - 只想导出单个对话，请在[对话列表]中点击[•••]' : ' - To export a single chat, click [•••] in [Chat List]'
                                    }}
                                </option>
                                <option v-if="!isSimpleMode" disabled value="help4">
                                    {{ settingsLanguage == 'cn' ? ' - 浏览器兼容性问题会导致失败' : ' - Browser compatibility issues may cause failure'
                                    }}
                                </option>
                            </select>
                            <button @click="importLocalStorageData" :disabled="!isLocalStorageSupported" class="btn"
                                    style="justify-content: start;width: auto;height: 24px; flex: 1 1 0%; margin: 4px 3px 1px 3px;"
                                    :style="{ minWidth: settingsLanguage == 'cn' ? '87px' : '98px' }">
                                {{ settingsLanguage == 'cn' ? '导入 (.json)' : 'Import (.json)' }}
                            </button>
                            <button @click="getLocalStorageSize" :disabled="!isLocalStorageSupported" class="btn"
                                    style="padding: 0px 4px; height: 24px; line-height: 24px; margin: 4px 2px 1px 3px;min-width: 23px;"
                                    :title="settingsLanguage == 'cn' ? '用量统计' : 'Usage statistics'">
                                <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"
                                     fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                     stroke-linejoin="round">
                                    <path d="M20.2 7.8l-7.7 7.7-4-4-5.7 5.7"/>
                                    <path d="M15 7h6v6"/>
                                </svg>
                            </button>
                        </div>
                    </div>


                    <!-- End:  A: 主体内容 content -->
                </div>
                <!-- End:  A: 滚动框架 scroll -->
            </div>

            <!-- dialog-bottom -->
            <div class="dialog-bottom">
            </div>

            <!-- End:  A: dialog -->
        </div>

        <!-- dialog-mask -->
        <div class="dialog-mask" @click="isShowDialog_Settings = false">
        </div>

        <!-- End:  dialog-wrapper A:
        设置 Settings -->
    </div>


    <!-- dialog-wrapper B: 提示词 Prompt Generator -->
    <div id="dialog-promptgenerator" class="dialog-wrapper" v-show="isShowDialog_PromptGenerator">
        <!-- B: dialog -->
        <div class="dialog" style="width: 95%;max-width: 800px; max-height: 680px;">
            <!-- B: 标题 header -->
            <div class="dialog-header">
                <div class="dialog-title">
                    <svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 24 24"
                         fill="currentColor" stroke="currentColor" stroke-width="0.6" stroke-linecap="round"
                         stroke-linejoin="round">
                        <path d="M19.7,23.5H4.3c-2.1,0-3.8-1.7-3.8-3.8V4.3c0-2.1,1.7-3.8,3.8-3.8h15.3c2.1,0,3.8,1.7,3.8,3.8v15.3    C23.5,21.8,21.8,23.5,19.7,23.5z M4.3,1.5c-1.6,0-2.8,1.3-2.8,2.8v15.3c0,1.6,1.3,2.8,2.8,2.8h15.3c1.6,0,2.8-1.3,2.8-2.8V4.3    c0-1.6-1.3-2.8-2.8-2.8H4.3z"/>
                        <path d="M10.4,19.2H7.3c-0.3,0-0.5-0.2-0.5-0.5V5.3c0-0.3,0.2-0.5,0.5-0.5h9.3c0.3,0,0.5,0.2,0.5,0.5v8.5c0,0.3-0.2,0.5-0.5,0.5    h-5.7v4.4C10.9,19,10.7,19.2,10.4,19.2z M7.8,18.2h2.1v-4.4c0-0.3,0.2-0.5,0.5-0.5h5.7V5.8H7.8V18.2z M13.6,11.2h-3.2    c-0.3,0-0.5-0.2-0.5-0.5V8.4c0-0.3,0.2-0.5,0.5-0.5h3.2c0.3,0,0.5,0.2,0.5,0.5v2.3C14.1,11,13.9,11.2,13.6,11.2z M10.9,10.2h2.2    V8.9h-2.2V10.2z"/>
                    </svg>
                    {{ settingsLanguage == 'cn' ? '&nbsp;提示词&nbsp;' : '&nbsp;Prompt Generator&nbsp;' }}
                    <button @click="changePromptGeneratorMaxWidth" class="btn bluebtn"
                            style="margin-right: 7px;width: 22px; padding: 0px 1px; height: 20px;"
                            :title="settingsLanguage == 'cn' ? '更改最大宽度' : 'Change Maximum Width'">
                        <div>
                            <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"
                                 fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                 stroke-linejoin="round">
                                <rect x="3" y="3" width="18" height="18" rx="2"/>
                                <path d="M3 9h18M9 21V9"/>
                            </svg>
                        </div>
                    </button>
                    <button @click="changePromptGeneratorLayout" class="btn bluebtn"
                            style="margin-right: auto;margin-left: 1px;width: 22px; padding: 0px 1px; height: 20px;"
                            :title="settingsLanguage == 'cn' ? '更改布局 (适用于移动设备)' : 'Change Layout (For Mobile)'">
                        <div>
                            <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"
                                 fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                 stroke-linejoin="round">
                                <rect x="3" y="3" width="18" height="18" rx="2"/>
                                <path d="M9 3v18"/>
                            </svg>
                        </div>
                    </button>
                </div>
                <div class="dialog-close" @click="isShowDialog_PromptGenerator = false"
                     :title="settingsLanguage == 'cn' ? '关闭' : 'Close'">
                    <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"
                         style="fill: none; stroke: #333; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;">
                        <path d="M6 18L18 6M6 6l12 12"></path>
                    </svg>
                </div>
            </div>

            <!-- B: 滚动框架 scroll -->
            <div class="dialog-scroll">
                <!-- B: 主体内容 content -->
                <div class="dialog-content" :style="styleDialogLayout">

                    <!-- panelgroup: 当前系统提示词  Current System Prompt -->
                    <div class="panelgroup">
                        <div class="panelgroup-head">
                            <span>{{ settingsLanguage == 'cn' ? '当前系统提示词 ( system prompt )' : 'Current System Prompt'
                                }} </span>
                            <button @click="showMiniHelp('B-CurrentSystemPrompt')" class="btn minibtn bluebtn btnzoom-b"
                                    style="margin-right: 4px;" :title="settingsLanguage == 'cn' ? '功能说明' : 'Help'">?
                            </button>
                            <span style="color:#f94000; display:block; margin-right: auto;font-size: 12px;">{{ settingsLanguage == 'cn' ? '对话ID: #' + (this.chatid == null ? '' : (this.chatid.toString().length > 20 ? (this.chatid.toString().slice(0, 20) + " ...") : this.chatid.toString().slice(0, 20))) : 'Chat ID: #' + (this.chatid == null ? '' : (this.chatid.toString().length > 20 ? (this.chatid.toString().slice(0, 20) + " ...") : this.chatid.toString().slice(0, 20)))
                                }}</span>
                        </div>

                        <div class="panelrow" style="align-items: end; height:auto;flex-wrap: wrap;">
                            <!-- 复制到编辑区 -->
                            <button @click="copySystemPromptToEditor" class="btn" style="margin-left: 5px;">
                                {{ settingsLanguage == 'cn' ? '复制到编辑框' : 'Copy To Editor' }}
                            </button>
                            <!-- 展开 -->
                            <button @click="changeElementHeight('textareaSPReadOnly')" class="btn bluebtn"
                                    style="width: 25px;padding: 0px 2px;margin: 0 5px;"
                                    :title="settingsLanguage == 'cn' ? '快速切换高度' : 'Quick Height Toggle'">
                                <svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
                                     width="18" height="18" viewBox="0 0 32 32" fill="none" stroke="currentColor"
                                     stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"
                                     stroke-miterlimit="10">
                                    <path d="m28,30l-24,0c-1.1,0 -2,-0.9 -2,-2l0,-24c0,-1.1 0.9,-2 2,-2l24,0c1.1,0 2,0.9 2,2l0,24c0,1.1 -0.9,2 -2,2z"
                                          id="svg_1"></path>
                                    <line id="svg_2" x1="20" x2="20" y1="2" y2="30"></line>
                                    <polyline points="27,24 25,26 23,24 "></polyline>
                                    <polyline points="23,8 25,6 27,8 "></polyline>
                                    <line x1="6" x2="16" y1="9" y2="9"></line>
                                    <line x1="6" x2="12" y1="13" y2="13"></line>
                                    <line x1="6" x2="12" y1="17" y2="17"></line>
                                </svg>
                            </button>
                            <!-- 换行视图 -->
                            <button @click="changeElementLineBreak('textareaSPReadOnly')" class="btn bluebtn"
                                    style="margin-right: auto;width: 25px;padding: 0px 2px;"
                                    :title="settingsLanguage == 'cn' ? '更改换行模式' : 'Change Wrap Mode'">
                                <svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
                                     width="18" height="18" viewBox="0 0 32 32" fill="none" stroke="currentColor"
                                     stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"
                                     stroke-miterlimit="10">
                                    <path d="m2,28l0,-24c0,-1.1 0.9,-2 2,-2l24,0c1.1,0 2,0.9 2,2l0,24c0,1.1 -0.9,2 -2,2l-24,0c-1.1,0 -2,-0.9 -2,-2z"
                                          id="svg_1"/>
                                    </path>
                                    <line x1="30" x2="2" y1="20" y2="20"/>
                                    </line>
                                    <polyline points="8,27 6,25 8,23 "/>
                                    </polyline>
                                    <polyline points="24,23 26,25 24,27 "/>
                                    </polyline>
                                    <line x1="6" x2="16" y1="9" y2="9"/>
                                    </line>
                                    <line x1="6" x2="12" y1="13" y2="13"/>
                                    </line>
                                </svg>
                            </button>
                            <!-- 一键复制按钮   Cpoy Button -->
                            <button class="copybtn" data-clipboard-nextelementsibling=""
                                    style="opacity: 1;position: relative;flex-shrink: 0;top: auto;right: auto;margin:0 5px 0 0 ;"
                                    :title="settingsLanguage == 'cn' ? '一键复制' : 'One-Click Copy'">
                                <div>
                                    <svg width="14" height="17" class="clippy" fill="currentColor">
                                        <path d="m2.36373,12.61912l4.12233,0l0,1.03058l-4.12233,0l0,-1.03058zm5.15291,-6.18349l-5.15291,0l0,1.03058l5.15291,0l0,-1.03058zm2.06116,3.09174l0,-2.06116l-3.09174,3.09174l3.09174,3.09174l0,-2.06116l5.15291,0l0,-2.06116l-5.15291,0zm-4.63761,-1.03058l-2.57645,0l0,1.03058l2.57645,0l0,-1.03058zm-2.57645,3.09174l2.57645,0l0,-1.03058l-2.57645,0l0,1.03058zm9.27523,1.03058l1.03058,0l0,2.06116c-0.0161,0.28985 -0.11272,0.53139 -0.30595,0.72463s-0.43478,0.28985 -0.72463,0.30595l-10.30581,0c-0.5636,0 -1.03058,-0.46698 -1.03058,-1.03058l0,-11.33639c0,-0.5636 0.46698,-1.03058 1.03058,-1.03058l3.09175,0c0,-1.1433 0.91786,-2.06116 2.06116,-2.06116s2.06116,0.91786 2.06116,2.06116l3.09174,0c0.5636,0 1.03058,0.46698 1.03058,1.03058l0,5.15291l-1.03058,0l0,-3.09174l-10.30581,0l0,9.27523l10.30581,0l0,-2.06116zm-9.27523,-8.24465l8.24465,0c0,-0.5636 -0.46698,-1.03058 -1.03058,-1.03058l-1.03058,0c-0.5636,0 -1.03058,-0.46698 -1.03058,-1.03058s-0.46698,-1.03058 -1.03058,-1.03058s-1.03058,0.46698 -1.03058,1.03058s-0.46698,1.03058 -1.03058,1.03058l-1.03058,0c-0.5636,0 -1.03058,0.46698 -1.03058,1.03058z">
                                        </path>
                                    </svg>
                                </div>
                            </button>

                            <!-- 输入框(只读) - 当前系统提示词 -->
                            <textarea readonly v-model="gptSystemPromptReadOnly" type="text"
                                      class="dh-input textareaSPReadOnly"
                                      style="flex: 0 0 100%;margin: 4px 0 0 0;height:100px;min-height:80px;max-height: 1000px;"
                                      placeholder=""></textarea>
                        </div>
                    </div>

                    <!-- panelgroup: 提示词编辑区  Edit Prompts -->
                    <div class="panelgroup">
                        <div class="panelgroup-head">
                            <span>{{ settingsLanguage == 'cn' ? '提示词编辑区' : 'Prompt Editor' }}</span>
                            <button @click="showMiniHelp('B-PromptEditor')" class="btn minibtn bluebtn btnzoom-b"
                                    style="margin-right: auto;" :title="settingsLanguage == 'cn' ? '功能说明' : 'Help'">
                                ?
                            </button>
                        </div>

                        <div class="panelrow" style="align-items: end; height:auto;flex-wrap: wrap;">
                            <!-- 保存为系统提示词 更新系统提示词 -->
                            <button @click="setSystemPrompt" :disabled="btnDisabledState_SetSystemPrompt" class="btn"
                                    style="margin-left: 5px;">
                                {{ settingsLanguage == 'cn' ? '保存为系统提示词' : 'Save as System Prompt' }}
                            </button>

                            <!-- 展开 -->
                            <button @click="changeElementHeight('textareasystemprompt')" class="btn bluebtn"
                                    style="width: 25px;padding: 0px 2px;margin: 0 5px;"
                                    :title="settingsLanguage == 'cn' ? '快速切换高度' : 'Quick Height Toggle'">
                                <svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
                                     width="18" height="18" viewBox="0 0 32 32" fill="none" stroke="currentColor"
                                     stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"
                                     stroke-miterlimit="10">
                                    <path d="m28,30l-24,0c-1.1,0 -2,-0.9 -2,-2l0,-24c0,-1.1 0.9,-2 2,-2l24,0c1.1,0 2,0.9 2,2l0,24c0,1.1 -0.9,2 -2,2z"
                                          id="svg_1"></path>
                                    <line id="svg_2" x1="20" x2="20" y1="2" y2="30"></line>
                                    <polyline points="27,24 25,26 23,24 "></polyline>
                                    <polyline points="23,8 25,6 27,8 "></polyline>
                                    <line x1="6" x2="16" y1="9" y2="9"></line>
                                    <line x1="6" x2="12" y1="13" y2="13"></line>
                                    <line x1="6" x2="12" y1="17" y2="17"></line>
                                </svg>
                            </button>
                            <!-- 换行视图 -->
                            <button @click="changeElementLineBreak('textareasystemprompt')" class="btn bluebtn"
                                    style="margin-right: auto;width: 25px;padding: 0px 2px;"
                                    :title="settingsLanguage == 'cn' ? '更改换行模式' : 'Change Wrap Mode'">
                                <svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
                                     width="18" height="18" viewBox="0 0 32 32" fill="none" stroke="currentColor"
                                     stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"
                                     stroke-miterlimit="10">
                                    <path d="m2,28l0,-24c0,-1.1 0.9,-2 2,-2l24,0c1.1,0 2,0.9 2,2l0,24c0,1.1 -0.9,2 -2,2l-24,0c-1.1,0 -2,-0.9 -2,-2z"
                                          id="svg_1"/>
                                    </path>
                                    <line x1="30" x2="2" y1="20" y2="20"/>
                                    </line>
                                    <polyline points="8,27 6,25 8,23 "/>
                                    </polyline>
                                    <polyline points="24,23 26,25 24,27 "/>
                                    </polyline>
                                    <line x1="6" x2="16" y1="9" y2="9"/>
                                    </line>
                                    <line x1="6" x2="12" y1="13" y2="13"/>
                                    </line>
                                </svg>
                            </button>
                            <!-- 一键复制按钮   Cpoy Button -->
                            <button class="copybtn" data-clipboard-nextelementsibling=""
                                    style="opacity: 1;position: relative;flex-shrink: 0;top: auto;right: auto;margin:0 5px 0 0 ;"
                                    :title="settingsLanguage == 'cn' ? '一键复制' : 'One-Click Copy'">
                                <div>
                                    <svg width="14" height="17" class="clippy" fill="currentColor">
                                        <path d="m2.36373,12.61912l4.12233,0l0,1.03058l-4.12233,0l0,-1.03058zm5.15291,-6.18349l-5.15291,0l0,1.03058l5.15291,0l0,-1.03058zm2.06116,3.09174l0,-2.06116l-3.09174,3.09174l3.09174,3.09174l0,-2.06116l5.15291,0l0,-2.06116l-5.15291,0zm-4.63761,-1.03058l-2.57645,0l0,1.03058l2.57645,0l0,-1.03058zm-2.57645,3.09174l2.57645,0l0,-1.03058l-2.57645,0l0,1.03058zm9.27523,1.03058l1.03058,0l0,2.06116c-0.0161,0.28985 -0.11272,0.53139 -0.30595,0.72463s-0.43478,0.28985 -0.72463,0.30595l-10.30581,0c-0.5636,0 -1.03058,-0.46698 -1.03058,-1.03058l0,-11.33639c0,-0.5636 0.46698,-1.03058 1.03058,-1.03058l3.09175,0c0,-1.1433 0.91786,-2.06116 2.06116,-2.06116s2.06116,0.91786 2.06116,2.06116l3.09174,0c0.5636,0 1.03058,0.46698 1.03058,1.03058l0,5.15291l-1.03058,0l0,-3.09174l-10.30581,0l0,9.27523l10.30581,0l0,-2.06116zm-9.27523,-8.24465l8.24465,0c0,-0.5636 -0.46698,-1.03058 -1.03058,-1.03058l-1.03058,0c-0.5636,0 -1.03058,-0.46698 -1.03058,-1.03058s-0.46698,-1.03058 -1.03058,-1.03058s-1.03058,0.46698 -1.03058,1.03058s-0.46698,1.03058 -1.03058,1.03058l-1.03058,0c-0.5636,0 -1.03058,0.46698 -1.03058,1.03058z">
                                        </path>
                                    </svg>
                                </div>
                            </button>

                            <!-- 输入框 - 编辑提示词 -->
                            <textarea @input="btnDisabledState_SetSystemPrompt=false;" v-model="gptSystemPrompt"
                                      type="text" class="dh-input textareasystemprompt"
                                      style="flex: 0 0 100%;margin: 4px 0 0 0;height:100px;min-height:80px;max-height: 1000px;"
                                      :placeholder=" settingsLanguage == 'cn' ? '输入提示词，保存后生效。如需取消系统提示词，请清空输入框后点保存。' : 'Input prompt: Changes will take effect after saving. To cancel system prompt, clear the input field and save.' "></textarea>
                        </div>
                    </div>


                    <!-- End:  B: 主体内容 content -->
                </div>
                <!-- End:  B: 滚动框架 scroll -->
            </div>

            <!-- dialog-bottom -->
            <div class="dialog-bottom">
            </div>

            <!-- End:  B: dialog -->
        </div>

        <!-- dialog-mask -->
        <div class="dialog-mask" @click="isShowDialog_PromptGenerator = false">
        </div>

        <!-- End:  dialog-wrapper B: 设置 Settings -->
    </div>


    <!-- dialog-wrapper C: 对话列表 Chat List -->
    <div id="dialog-ChatList" class="dialog-wrapper" v-show="isShowDialog_ChatList">
        <!-- C: dialog -->
        <div class="dialog" style="max-width: 430px;width: 95%;max-height: 720px;background-color: rgb(237, 237, 237);">
            <!-- C: 标题 header -->
            <div class="dialog-header">
                <div class="dialog-title" style="width: calc(95% - 35px);">
                    <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"
                         stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
                    </svg>
                    {{ settingsLanguage == 'cn' ? '&nbsp;对话列表&nbsp;' : '&nbsp;Chat List&nbsp;' }}
                    <button @click="showMiniHelp('C-ChatList')" class="btn minibtn bluebtn btnzoom-b"
                            style="margin-right: 8px;" :title="settingsLanguage == 'cn' ? '功能说明' : 'Help'">?
                    </button>
                    <button @click="changeChatListMaxWidth" class="btn bluebtn"
                            style="margin-right: 8px;width: 22px; padding: 0px 1px; height: 20px;"
                            :title="settingsLanguage == 'cn' ? '更改最大宽度' : 'Change Maximum Width'">
                        <div>
                            <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"
                                 fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                 stroke-linejoin="round">
                                <rect x="3" y="3" width="18" height="18" rx="2"/>
                                <path d="M3 9h18M9 21V9"/>
                            </svg>
                        </div>
                    </button>
                    <button @click="changeChatListTransparent" class="btn bluebtn"
                            style="margin-right: auto;width: 22px; padding: 0px 1px; height: 20px;"
                            :title="settingsLanguage == 'cn' ? '更改透明度' : 'Change Transparency'">
                        <div>
                            <svg xmlns="http://www.w3.org/2000/svg" width="17" height="16" viewBox="0 0 24 24"
                                 fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                 stroke-linejoin="round">
                                <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
                                <circle cx="12" cy="12" r="3"></circle>
                            </svg>
                        </div>
                    </button>
                    <button @click="chatList_addNewChatData(false)" class="btn"
                            style="width: auto; padding: 0px 8px; height: 20px; line-height: 20px; border-radius: 5px; font-size: 13px; margin-left: 8px;">
                        <div>
                            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
                                 fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                 stroke-linejoin="round">
                                <path d="M3 3h18v18H3zM12 8v8m-4-4h8"/>
                            </svg>
                        </div>
                        {{ settingsLanguage == 'cn' ? '&nbsp;开始新的对话&nbsp;' : '&nbsp;Start a new chat' }}
                    </button>
                </div>
                <div class="dialog-close" @click="isShowDialog_ChatList = false"
                     :title="settingsLanguage == 'cn' ? '关闭' : 'Close'">
                    <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"
                         style="fill: none; stroke: #333; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;">
                        <path d="M6 18L18 6M6 6l12 12"></path>
                    </svg>
                </div>
            </div>

            <!-- C: 滚动框架 scroll -->
            <div class="dialog-scroll">
                <!-- C: 主体内容 content -->
                <div class="dialog-content">

                    <!-- panelgroup: 单个对话 Chat Data -->
                    <div class="panelgroup" v-for="chatdata in chatDataList" :key="chatdata.chatcreatedtime"
                         @click="chatList_ClickChatData(chatdata)"
                         :class="{'panelgroup-chatdata-selected': (chatdata.chatcreatedtime === selectedChatData.chatcreatedtime && !isChatListTransparent) ,'panelgroup-chatdata-selected-transparent':  (chatdata.chatcreatedtime === selectedChatData.chatcreatedtime && isChatListTransparent) }">
                        <div class="panelgroup-head">
                            <!--  对话创建时间 Chat Creation Time -->
                            <span style="margin-right: auto;"
                                  :style="{ color: isChatListTransparent ? 'rgba(204, 204, 204, 0.3)' : '#444444' }">{{ (settingsLanguage == 'cn' ? '创建时间: ' : 'Creation Date: ') + (chatdata.chatcreatedtime === null ? (settingsLanguage == 'cn' ? '新对话' : 'New Chat') : (chatdata.chatcreatedtime.startsWith(current_Year) ? chatdata.chatcreatedtime.slice(5) : chatdata.chatcreatedtime))
                                }}</span>
                            <!--  占用存储空间大小 Storage Usage  *可考虑增加显示条件： display none - this.isLocalStorageAllowed && this.isLocalStorageSupported -->
                            <button class="btn minibtn"
                                    style="display: flex;width: auto; height: 16px; padding: 0px;margin-right: 0px; border-radius: 4px;cursor:auto;border: none !important; background: none !important;"
                                    :style="{ color: isChatListTransparent ? 'rgba(204, 204, 204, 0.3)' : '#444444' , display: chatdata.chatcreatedtime === null ? 'none' : 'flex' }"
                                    :title="settingsLanguage == 'cn' ? '占用存储空间大小' : 'Storage Usage'">
                                <svg xmlns="http://www.w3.org/2000/svg" width="12" height="13" viewBox="0 0 24 24"
                                     fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                     stroke-linejoin="round">
                                    <ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
                                    <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
                                    <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
                                </svg>
                                <span :style="{ color: isChatListTransparent ? 'rgba(204, 204, 204, 0.3)' : '#444444' }">{{ (JSON.stringify(chatdata).length * 2 / 1024 / 1024).toFixed(2)}} MB</span>
                            </button>
                            <!--  goto Top -->
                            <button @click="gotoTop" class="btn minibtn btnzoom-c"
                                    :style="{ display:  (chatdata.chatcreatedtime === null || chatdata.chatcreatedtime !== selectedChatData.chatcreatedtime)  ? 'none' : 'flex'}"
                                    style="width: 18px; height: 16px; padding: 0px; border-radius: 4px;"
                                    :title="settingsLanguage == 'cn' ? '滚动到顶部' : 'Scroll to Top'">
                                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
                                     fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                     stroke-linejoin="round">
                                    <path d="M17 11l-5-5-5 5M17 18l-5-5-5 5"/>
                                </svg>
                            </button>
                            <!--  goto Bottom  -->
                            <button @click="gotoBottom" class="btn minibtn btnzoom-c"
                                    :style="{ display:  (chatdata.chatcreatedtime === null || chatdata.chatcreatedtime !== selectedChatData.chatcreatedtime)  ? 'none' : 'flex'}"
                                    style="width: 18px; height: 16px; padding: 0px; border-radius: 4px;margin-left: 1px;"
                                    :title="settingsLanguage == 'cn' ? '滚动到底部' : 'Scroll to Bottom'">
                                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
                                     fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                     stroke-linejoin="round">
                                    <path d="M7 13l5 5 5-5M7 6l5 5 5-5"/>
                                </svg>
                            </button>
                            <!--  更多选项 More Options -->
                            <select @change="changeSelectedChatListMoreOptions(chatdata)"
                                    class="dropdown minibtn minibtn-default btnzoom-c"
                                    v-model="selectedChatListMoreOptions"
                                    :style="{ display: chatdata.chatcreatedtime === null  ? 'none' : 'block' }"
                                    style="width:31px;" :title="settingsLanguage == 'cn' ? '更多选项' : 'More Options'">
                                <option value="">•••</option>
                                <option v-if="settingsLanguage == 'cn'"
                                        v-for="option in selectedChatListMoreOptions_Options_cn" :value="option.op">
                                    {{ option.label }}
                                </option>
                                <option v-else v-for="option in selectedChatListMoreOptions_Options_en"
                                        :value="option.op">{{ option.label }}
                                </option>
                                <option value="EraseLogs">5)
                                    {{ (settingsLanguage == 'cn' ? '抹掉日志(Markdown格式, ≈' : 'Erase logs (Markdown, ≈') + (chatdata.chatVariables.chathistory.length * 2 / 1024 / 1024).toFixed(2) + 'MB)'
                                    }}
                                </option>
                            </select>
                            <!--  删除 delete -->
                            <button @click="deleteChatData(chatdata)" class="btn minibtn redbtn btnzoom-c"
                                    style="display: flex; width: 16px; height: 16px; padding: 0px; border-radius: 4px;"
                                    :title="settingsLanguage == 'cn' ? '删除对话' : 'Delete Chat'">
                                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
                                     fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                     stroke-linejoin="round">
                                    <line x1="18" y1="6" x2="6" y2="18"></line>
                                    <line x1="6" y1="6" x2="18" y2="18"></line>
                                </svg>
                            </button>
                        </div>

                        <div class="panelrow" style="align-items: end; height:auto;flex-wrap: wrap;">
                            <!-- ID   *old::: background: linear-gradient(to bottom, #fcfcfc, #f0f0f0) !important;  -->
                            <button @click="chatList_EditChatID(chatdata)" class="btn btnzoom-c"
                                    style="width: auto;padding: 0px 8px; background: none !important; color: #1aad19 !important;"
                                    :style="{ border: isChatListTransparent ? '1px solid rgba(221, 221, 221, 0.3) !important' : '1px solid #DDD !important' }"
                                    :title="settingsLanguage == 'cn' ? '对话ID' : 'Chat ID'">
                                <div>
                                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
                                         fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"
                                         stroke-linejoin="round">
                                        <line x1="4" y1="9" x2="20" y2="9"></line>
                                        <line x1="4" y1="15" x2="20" y2="15"></line>
                                        <line x1="10" y1="3" x2="8" y2="21"></line>
                                        <line x1="16" y1="3" x2="14" y2="21"></line>
                                    </svg>
                                </div>
                                <span style="color: #1aad19;font-size: 13px;">&nbsp;{{ chatdata.chatid }}</span>
                            </button>
                            <!-- 上次活动时间 Last Activity Time -->
                            <button disabled class="btn"
                                    style="margin-right: auto;width: auto;padding: 0px 8px;cursor:auto;border: none !important; background: none !important;"
                                    :style="{ display: chatdata.chatcreatedtime === null  ? 'none' : 'flex'}">
                                <div>
                                    <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"
                                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                         stroke-linejoin="round">
                                        <circle cx="12" cy="12" r="10"></circle>
                                        <polyline points="12 6 12 12 16 14"></polyline>
                                    </svg>
                                </div>
                                <span :style="{ color: isChatListTransparent ? 'rgba(204, 204, 204, 0.3)' : '#444444' }">&nbsp;{{ (settingsLanguage == 'cn' ? '上次活动时间: ' : 'Last Activity Time: ') + (chatdata.chatlasttime === null ? (settingsLanguage == 'cn' ? '新对话' : 'New Chat') : (chatdata.chatlasttime.startsWith(current_Year) ? chatdata.chatlasttime.slice(5) : chatdata.chatlasttime))
                                    }}</span>
                            </button>
                        </div>

                        <div class="panelrow" style="align-items: end; height:auto;flex-wrap: wrap;">
                            <!-- 标题 备注 描述 Title -->
                            <button @click="chatList_EditChatTitle(chatdata)" class="btn btnzoom-c"
                                    style="width: auto;padding: 0px 8px; background: none !important; color: #1aad19 !important;"
                                    :style="{ border: isChatListTransparent ? '1px solid rgba(221, 221, 221, 0.3) !important' : '1px solid #DDD !important' }"
                                    :title="settingsLanguage == 'cn' ? '对话标题 / 描述' : 'Chat Title / Description'">
                                <div>
                                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
                                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                         stroke-linejoin="round">
                                        <path d="M20 14.66V20a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h5.34"></path>
                                        <polygon points="18 2 22 6 12 16 8 16 8 12 18 2"></polygon>
                                    </svg>
                                </div>
                                <span :style="{ color: isChatListTransparent ? 'rgba(204, 204, 204, 0.3)' : (chatdata.chattitle == '' ? '#cccccc' : '#444444') }">&nbsp;{{ chatdata.chattitle == '' ? (settingsLanguage == 'cn' ? '尚未写描述' : 'No description yet.') : chatdata.chattitle
                                    }}</span>
                            </button>
                        </div>
                    </div>


                    <!-- End:  C: 主体内容 content -->
                </div>
                <!-- End:  C: 滚动框架 scroll -->
            </div>

            <!-- dialog-bottom -->
            <div class="dialog-bottom">
            </div>

            <!-- End:  C: dialog -->
        </div>

        <!-- dialog-mask -->
        <div class="dialog-mask" @click="isShowDialog_ChatList = false">
        </div>

        <!-- End:  dialog-wrapper C: 对话列表 Chat List -->
    </div>


    <!-- dialog-wrapper D: 轮询 / 自动轮换API Key / Auto Key Rotation / Auto Key Rotation Management-->
    <div id="dialog-AutoKeyRotation" class="dialog-wrapper" v-show="isShowDialog_AutoKeyRotation">
        <!-- D: dialog -->
        <div class="dialog" style="max-width: 520px;width: 90%;max-height: 720px;background-color: rgb(237, 237, 237);">
            <!-- D: 标题 header -->
            <div class="dialog-header">
                <div class="dialog-title" style="width: calc(95% - 35px);">
                    <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"
                         stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"/>
                    </svg>
                    {{ settingsLanguage == 'cn' ? '&nbsp;自动轮换API Key&nbsp;' : '&nbsp;Auto Key Rotation&nbsp;' }}
                    <button @click="showMiniHelp('D-AutoKeyRotation')" class="btn minibtn bluebtn btnzoom-b"
                            style="margin-right: 8px;" :title="settingsLanguage == 'cn' ? '功能说明' : 'Help'">?
                    </button>
                    <button @click="changeAutoKeyRotationSorted" class="btn bluebtn"
                            style="margin-right: auto;width: 22px; padding: 0px 1px; height: 20px;"
                            :title="settingsLanguage == 'cn' ? '三种排序方式以供查看' : 'Three ways to sort for viewing'">
                        <div>
                            <svg xmlns="http://www.w3.org/2000/svg" width="19" height="18" viewBox="0 0 24 24"
                                 fill="currentColor">
                                <path d="M7 20h2V8h3L8 4 4 8h3zm13-4h-3V4h-2v12h-3l4 4z"/>
                            </svg>
                        </div>
                    </button>
                    <button @click="autoKeyRotation_BulkAdd" class="btn"
                            style="width: auto; padding: 0px 8px; height: 20px; line-height: 20px; border-radius: px; font-size: 13px; margin-left: 8px;">
                        <div>
                            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
                                 fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                 stroke-linejoin="round">
                                <line x1="8" y1="6" x2="21" y2="6"></line>
                                <line x1="8" y1="12" x2="21" y2="12"></line>
                                <line x1="8" y1="18" x2="21" y2="18"></line>
                                <line x1="3" y1="6" x2="3.01" y2="6"></line>
                                <line x1="3" y1="12" x2="3.01" y2="12"></line>
                                <line x1="3" y1="18" x2="3.01" y2="18"></line>
                            </svg>
                        </div>
                        {{ settingsLanguage == 'cn' ? '&nbsp;批量添加&nbsp;' : '&nbsp;Bulk Add' }}
                    </button>
                </div>
                <div class="dialog-close" @click="isShowDialog_AutoKeyRotation = false"
                     :title="settingsLanguage == 'cn' ? '关闭' : 'Close'">
                    <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"
                         style="fill: none; stroke: #333; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;">
                        <path d="M6 18L18 6M6 6l12 12"></path>
                    </svg>
                </div>
            </div>

            <!-- D: 滚动框架 scroll -->
            <div class="dialog-scroll">
                <!-- D: 主体内容 content -->
                <div class="dialog-content">
                    <!-- panelgroup: 批量添加 Bulk Add -->
                    <div class="panelgroup" :style="{ display: isShowBulkAddPanel_AutoKeyRotation ? 'block' : 'none' }"
                         style="margin-bottom: 18px;">
                        <div class="panelgroup-head">
                            <span>{{ settingsLanguage == 'cn' ? '批量添加API Key' : 'Bulk Add API Keys' }}</span>
                        </div>

                        <div class="panelrow" style="align-items: end; height:auto;flex-wrap: wrap;">
                            <!-- 提交 -->
                            <button @click="submitBulkApiKeys(true,true,null)" class="btn" style="margin-left: 5px;">
                                {{ settingsLanguage == 'cn' ? '提交' : 'Submit' }}
                            </button>
                            <!-- 取消 -->
                            <button @click="isShowBulkAddPanel_AutoKeyRotation = false" class="btn redbtn"
                                    style="margin-left: 5px;margin-right: auto;">
                                {{ settingsLanguage == 'cn' ? '取消' : 'Cancel' }}
                            </button>

                            <!-- 输入框 -->
                            <textarea type="text" class="dh-input textareabulkaddapikey"
                                      style="font-size: 13px;flex: 0 0 100%;margin: 4px 0 0 0;height:108px;min-height:55px;max-height: 500px;white-space: pre;"
                                      :placeholder=" settingsLanguage == 'cn' ? '输入以 &quot;sk-&quot; 开头的 API Key，每行一个。' : 'Enter API Keys starting with &quot;sk-&quot; , one per line.' "></textarea>
                        </div>
                    </div>

                    <!-- panelgroup: apidata -->
                    <div class="panelgroup" v-for="apidata in apiCheckedData" :key="apidata.apikey">
                        <div class="panelgroup-head">
                            <!--  最近更新时间 updatetime -->
                            <span style="color: '#444444';">{{ settingsLanguage == 'cn' ? '更新时间: ' : 'Update Time: '}}</span>
                            <span style="margin-right: auto;"
                                  :style="{color: apidata.updatetime === 0 ? '#0096b6' : '#444444' }">{{ apidata.updatetime === 0 ? (settingsLanguage == 'cn' ? '新密钥' : 'New API Key') : (formatDateYYYYMMDDHHmmss(new Date(apidata.updatetime), ':').startsWith(current_Year) ? formatDateYYYYMMDDHHmmss(new Date(apidata.updatetime), ':').slice(5) : formatDateYYYYMMDDHHmmss(new Date(apidata.updatetime), ':'))
                                }}</span>
                            <!--  删除 delete -->
                            <button @click="deleteApiData(apidata)" class="btn minibtn redbtn btnzoom-c"
                                    style="display: flex; width: 16px; height: 16px; padding: 0px; border-radius: 4px;"
                                    :title="settingsLanguage == 'cn' ? '删除API Key' : 'Delete Key'">
                                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
                                     fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                     stroke-linejoin="round">
                                    <line x1="18" y1="6" x2="6" y2="18"></line>
                                    <line x1="6" y1="6" x2="18" y2="18"></line>
                                </svg>
                            </button>
                        </div>

                        <div class="panelrow" style="align-items: end; height:auto;flex-wrap: wrap;">
                            <!-- 级别 -->
                            <button @click="autoKeyRotation_EditLevel(apidata)" class="btn btnzoom-c"
                                    style="width: auto;padding: 0 8px;border: 1px solid #DDD !important; background: none; color: #1aad19 !important;"
                                    :title="settingsLanguage == 'cn' ? '级别' : 'Level'">
                                <div>
                                    <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"
                                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                         stroke-linejoin="round">
                                        <line x1="4" y1="21" x2="4" y2="14"></line>
                                        <line x1="4" y1="10" x2="4" y2="3"></line>
                                        <line x1="12" y1="21" x2="12" y2="12"></line>
                                        <line x1="12" y1="8" x2="12" y2="3"></line>
                                        <line x1="20" y1="21" x2="20" y2="16"></line>
                                        <line x1="20" y1="12" x2="20" y2="3"></line>
                                        <line x1="1" y1="14" x2="7" y2="14"></line>
                                        <line x1="9" y1="8" x2="15" y2="8"></line>
                                        <line x1="17" y1="16" x2="23" y2="16"></line>
                                    </svg>
                                </div>
                                <span style="font-size: 13px;"
                                      :style="{ color: apidata.level === null ? '#0096b6' : apidata.level === 8 ? '#f42828' : apidata.level === 9 ? '#ff7d0b' : '#1aad19' }">&nbsp;{{ apidata.level === null ? (settingsLanguage == 'cn' ? '等待验证' : 'Pending Check') : apidata.level === 0 ? (settingsLanguage == 'cn' ? '付费账号' : 'Paid Account') : apidata.level === 8 ? (settingsLanguage == 'cn' ? '无效密钥' : 'Invalid key') : apidata.level === 9 ? (settingsLanguage == 'cn' ? '超出配额' : 'Quota exceeded') : (settingsLanguage == 'cn' ? apidata.level + ' 级' : 'Level ' + apidata.level)
                                    }}</span>
                            </button>
                            <!-- 成功率 -->
                            <button class="btn"
                                    style="margin-right: auto;width: auto;padding: 0px 14px;border: none; background: none; color: #1aad19 !important;cursor:auto;"
                                    :title="settingsLanguage == 'cn' ? '成功率' : 'Success rate'">
                                <div>
                                    <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"
                                         fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                         stroke-linejoin="round">
                                        <path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"></path>
                                    </svg>
                                </div>
                                <span style="color: #1aad19;font-size: 13px;">&nbsp;{{ Math.round(apidata.succrate * 100) + ' %'
                                    }}</span>
                            </button>
                        </div>

                        <div class="panelrow"
                             style="align-items: start; height:auto;flex-wrap: nowrap;justify-content: start;">
                            <!-- API Key-->
                            <div style="width: auto;padding: 0 1px 0 12px;color: #1aad19 !important;"
                                 :title="settingsLanguage == 'cn' ? 'API Key' : 'API Key'">
                                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
                                     fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                                     stroke-linejoin="round">
                                    <rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
                                    <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
                                </svg>
                            </div>
                            <span style="color: #444444;word-break: break-all;"
                                  :title="settingsLanguage == 'cn' ? 'API Key' : 'API Key'">&nbsp;{{ apidata.apikey
                                }}</span>
                        </div>
                    </div>


                    <!-- End:  D: 主体内容 content -->
                </div>
                <!-- End:  D: 滚动框架 scroll -->
            </div>

            <!-- dialog-bottom -->
            <div class="dialog-bottom">
            </div>

            <!-- End:  D: dialog -->
        </div>

        <!-- dialog-mask -->
        <div class="dialog-mask" @click="isShowDialog_AutoKeyRotation = false">
        </div>

        <!-- End:  dialog-wrapper D: 轮询 / 自动轮换API Key / Auto Key Rotation -->
    </div>


    <!-- Notification Z: 消息通知 Notification -->
    <div v-show="isShowNotification" class="notification">
        <div class="notification-wrapper"
             :class="{'notification-center': isNotificationHasMask, 'notification-bottom': !isNotificationHasMask }">
            <!-- Z: 标题 header :Only center -->
            <div :style="{ display: isNotificationHasMask ? 'block' : 'none' }"
                 style="position: relative;height: 30px;padding: 2px 16px;border-top-left-radius:10px;border-top-right-radius:10px;">
                <div class="dialog-title" style="color: #664909;font-weight:600;">
                    {{setNotificationTitle}}
                </div>
                <div class="dialog-close" @click="hideNotification" style="color: #664909;width: 18px;height: 18px;"
                     :title="settingsLanguage == 'cn' ? '关闭' : 'Close'">
                    <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"
                         style="fill: none; stroke: #333; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;">
                        <path d="M6 18L18 6M6 6l12 12"></path>
                    </svg>
                </div>
            </div>

            <!-- Z: 滚动框架 scroll -->
            <div class="dialog-scroll"
                 :style="{ height: isNotificationHasMask ? 'calc(100% - 49.5px)' : 'auto', overflowX: isNotificationHasMask ? 'auto' : 'hidden', overflowY: isNotificationHasMask ? 'scroll' : 'hidden' }">
                <!-- Z: 内容 content-->
                <div class="notification-content"
                     :class="{'notification-content-center': isNotificationHasMask, 'notification-content-bottom': !isNotificationHasMask }">
                    <!-- Z: 复制按钮 Copy btn -->
                    <button class="copybtn btncopynotification"
                            :style="{ display: isNotificationHasMask ? 'block' : 'none' }"
                            data-clipboard-nextelementsibling=""
                            style="opacity: 1; position: fixed; flex-shrink: 0; top: 7px; right: 40px; width: 25px; height: 25px;color: #664909; border: 1px solid #cfd450; background-color: #e5e881; background-image: linear-gradient(#fdffb4, #e5e881);"
                            :title="settingsLanguage == 'cn' ? '一键复制' : 'One-Click Copy'">
                        <div>
                            <svg width="14" height="17" class="clippy" fill="currentColor">
                                <path d="m2.36373,12.61912l4.12233,0l0,1.03058l-4.12233,0l0,-1.03058zm5.15291,-6.18349l-5.15291,0l0,1.03058l5.15291,0l0,-1.03058zm2.06116,3.09174l0,-2.06116l-3.09174,3.09174l3.09174,3.09174l0,-2.06116l5.15291,0l0,-2.06116l-5.15291,0zm-4.63761,-1.03058l-2.57645,0l0,1.03058l2.57645,0l0,-1.03058zm-2.57645,3.09174l2.57645,0l0,-1.03058l-2.57645,0l0,1.03058zm9.27523,1.03058l1.03058,0l0,2.06116c-0.0161,0.28985 -0.11272,0.53139 -0.30595,0.72463s-0.43478,0.28985 -0.72463,0.30595l-10.30581,0c-0.5636,0 -1.03058,-0.46698 -1.03058,-1.03058l0,-11.33639c0,-0.5636 0.46698,-1.03058 1.03058,-1.03058l3.09175,0c0,-1.1433 0.91786,-2.06116 2.06116,-2.06116s2.06116,0.91786 2.06116,2.06116l3.09174,0c0.5636,0 1.03058,0.46698 1.03058,1.03058l0,5.15291l-1.03058,0l0,-3.09174l-10.30581,0l0,9.27523l10.30581,0l0,-2.06116zm-9.27523,-8.24465l8.24465,0c0,-0.5636 -0.46698,-1.03058 -1.03058,-1.03058l-1.03058,0c-0.5636,0 -1.03058,-0.46698 -1.03058,-1.03058s-0.46698,-1.03058 -1.03058,-1.03058s-1.03058,0.46698 -1.03058,1.03058s-0.46698,1.03058 -1.03058,1.03058l-1.03058,0c-0.5636,0 -1.03058,0.46698 -1.03058,1.03058z">
                                </path>
                            </svg>
                        </div>
                    </button>

                    <!-- Z: 消息内容 提示框内容 Message -->
                    <span>{{setNotificationContent}}</span>

                    <!-- Z: 小按钮 关闭  :Only bottom -->
                    <div @click="hideNotification"
                         :style="{ display: !isNotificationHasMask && isNotificationBottomShowCloseBtn ? 'block' : 'none' } "
                         style="cursor: pointer; margin-left: 5px;margin-right: -3px; font-size: 12px;">×
                    </div>
                </div>
                <!-- End:  Z: 滚动框架 scroll -->
            </div>

            <!-- dialog-bottom -->
            <div class="dialog-bottom" :style="{ display: isNotificationHasMask ? 'block' : 'none' }">
            </div>

        </div>

        <!-- Z: 遮罩 Mash :Only center -->
        <div class="notification-mask" v-show="isNotificationHasMask" @click="hideNotification"></div>
        <!-- End:  Notification Z: 消息通知 Notification -->
    </div>


    <!-- End:  #app -->
</div>


<script>
    //Vue备选CDN(线路3) 再次检测，确保vue的正确加载。
    !window.Vue && document.write(unescape('%3Cscript src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.3.4/vue.global.prod.min.js"%3E%3C/script%3E'));

    //*当用户使用过时的旧版本浏览器访问时，会出现不支持的情况（不支持新的js代码/不支持新版vue.js） When accessing with an old version of browser, it may not be supported (not supporting ES6 |  not supporting new version of Vue.js).
    //定时器：12秒后添加按钮  Add button after 12 seconds.
    var timermask = setTimeout(function () {
        var loadingMask = document.getElementById("loading-mask");
        if (loadingMask) {
            var maskfirstChild = loadingMask.firstChild;
            if (maskfirstChild) {
                loadingMask.removeChild(maskfirstChild);
            }
            var maskbutton = document.createElement("button");
            maskbutton.innerHTML = "页面加载时间较长，您可以尝试刷新页面或检查网络连接是否畅通。<br>[点击我]点击文本可以隐藏遮罩，查看网页。在排除网络不畅/服务器拥堵等网络问题后，若还出现布局错误或乱码，可能是因为浏览器版本过旧或设备过旧，请尝试使用更新的浏览器版本。<br><br>The page is loading too slowly. Please refresh the page or check your internet connection.<br>Click [here] to hide the mask and view the webpage. After ruling out network issues such as poor connection or server congestion, if the layout is still incorrect or displaying gibberish, please try using a newer version of the browser or device.";
            maskbutton.onclick = displaymaskbtn;
            loadingMask.appendChild(maskbutton);
        }
        clearTimeout(timermask);
    }, 12000);

    // 隐藏遮罩
    function displaymaskbtn() {
        var loadingMask = document.getElementById("loading-mask");
        if (loadingMask) {
            loadingMask.classList.add('dialog-wrapper-hide');
        }
    }
</script>

<script>
    //一键复制按钮的提示框 Tooltips for Copy Button. by clipboardjs  https://clipboardjs.com/assets/scripts/tooltips.js
    //这段js放前面：网页加载过快会导致添加监听事件失败。This JS code should be placed at the beginning: If the webpage loads too quickly, listener events may fail to be added.

    //当前语言 Current Language。 全局变量。 Global variable.
    window.currentlang = 'cn';

    function addEventListenerCopyBtnTooltip(btn) {
        btn.addEventListener('mouseleave', clearCopyBtnTooltip);
        btn.addEventListener('blur', clearCopyBtnTooltip);
    }

    function clearCopyBtnTooltip(e) {
        e.currentTarget.setAttribute('class', 'copybtn');
        e.currentTarget.removeAttribute('aria-label');
    }

    //提示框方向的样式 directionClass: https://primer.style/css/components/tooltips
    function showCopyBtnTooltip(elem, msg, directionClass) {
        elem.setAttribute('class', 'copybtn tooltipped ' + directionClass);
        elem.setAttribute('aria-label', msg);
    }

    function fallbackCopyBtnMessage(action) {
        let actionMsg = '';
        actionMsg = window.currentlang == 'cn' ? '无数据 ' : 'No data'; //去除“不支持” Delete “No support”
        //actionMsg = window.currentlang == 'cn' ? '无数据 / 不支持' : 'No data / No support' ;
        return actionMsg;
    }
</script>

<script>
    if (!window.Vue) {
        alert("错误：\r\nVue.js加载失败，将无法使用GPT！请检查网络后刷新页面。\r\n\r\nError:\r\nFailed to load plugin Vue.js. The GPT will be unavailable. Please check your network and refresh the page to try again.")
    }
    ;

    const {createApp} = Vue;

    createApp({
        data() {
            return {
                api: '',
                default_apiKeyArray_WrittenInTheCode: [
                    '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'
                ],
                wsUrl: 'ws://127.0.0.1:10011/blog/websocket/message/',
                webSocket: null,
                userId: null, //用户ID
                sentext: '发送',
                lockReconnect: false,    // 是否建立连接
                reconnectTime: 60 * 1000 * 3,  //单位毫秒   60 * 1000 * 3 3分钟一次心跳
                reconnectTimeoutObj: undefined,
                serviceTimeoutObj: undefined,
                timeoutNum: undefined,

                onlineUserNum: 0,      // 在线用户数量

                // 网页上的第一条信息/欢迎语，不属于上下文。 这条信息只会在网页中显示，不会回传给服务器，不属于聊天记录。
                thefirstmessage: "### **若依聊天室** ***v4.7.6*** \r\n##### **v4.7.6** 基于ruoyi-fast开发，小而美的个人博客，后续还会新增更多好玩的更能和优化现有的功能，快来体验吧。 \r\n###### **v4.7.6** Developed based on ruoyi-fast, a small and beautiful personal blog. In the future, there will be more fun and optimized features to be added. Come and experience it now.\r\n\r\n_RUOYI-BLOG - small and beautiful_ \r\n###### _Change Language: [Settings] > [Language]_ \r\n```\r\nIf you like it, please give me a star on my Gitee repository.\r\n```\r\n[![](/img/github-C121011.svg)](https://gitee.com/GiteeKey/ruoyi-blog/) [![](/img/Gitee-C71D23.svg)](https://gitee.com/GiteeKey/ruoyi-blog/) ",

                btnDisabledState_Sending: true,
                btnDisabledState_CheckAPI: false,
                btnDisabledState_Clear: true,
                btnDisabledState_Undo: true,

                apibtntext: '<< 验证', // Check API key
                timerId: null, // *是否曾验证过apikey有借用此变量进行判断

                btnDisabledState_Export: true,
                //settingsContextualMode 也与chat data相关，写在后面
                hasSystemPromptBeenSaved: false,
                userMsgTokensForRetry: 0, //int 临时记录重问时的tokens数据
                succQA_Count: 0, //int 当前有效的上下文数量，一问一答succQA_Count = 2 。 数量变化对应this.msgContent
                totaltokens: 0, //int
                chathistory: '# GPT聊天记录 / Chat History  \r\n\r\n',
                //gptSystemPromptReadOnly 也与chat data相关，写在后面

                btnDisabledState_SaveApiURL: true,
                btnDisabledState_ApiURL: false,
                btnDisabledState_Language: false,
                btnDisabledState_ContextualMode: false,
                btnDisabledState_updateUserImageUrl: true,
                btnDisabledState_updateGPTImageUrl: true,
                isRetry_RetryMessage: '', //重问时传递重问的内容，避免使用this.msg
                isfirstclickonchatinputbox: true, //是否首次点击聊天输入框，防止重复激活点击事件
                isShowTotaltokensSVG: true, //Total Tokens 图标
                isShowTotaltokensLabel: true, //Total Tokens 文字
                isShowQACountLabel: true, //Q&A Count  文字
                isSendingNow: false, //是否正在发送消息
                isCheckingApiKeyNow: false, //是否正在验证API Key
                is_Alert_Confirm_Prompt_Open: false, //是否有原生弹窗弹出
                autoRetry_FirstTime: 0, //首次自动重发消息的时间戳，用于自动重问
                autoRetry_log: '',//自动重发过程的简单记录，用于自动重问
                autoRetry_apiKeyExcludeList: [], //临时记录需要暂时排除的apikey，[{'apikey':'****', 'time':0 }]
                current_Year: '2023', //String类型，用于比对日期，如果日期是今年的，那么年份不显示。 改变量自动更新，不用改
                bugfixing_BaiduInput_NewLine_timestamp: 0, //某些机型下，百度输入法，聊天框中无法换行


                //提示词窗口 Prompt Generator
                isShowDialog_PromptGenerator: false, //是否显示提示词窗口
                styleDialogLayout: {padding: '20px 20px 20px 20px'},

                gptSystemPromptReadOnly: '', //展示当前系统提示词(只读)
                btnDisabledState_SetSystemPrompt: false,
                gptPrompts: [{
                    prompt: '',
                    sort: -1,
                    id: -1,
                    tag: '',
                    note: '',
                    owner: 'me',
                    other1: '',
                    other2: ''
                }],


                //对话列表窗口 Chat List
                isShowDialog_ChatList: false,//是否显示对话列表窗口
                selectedChatListMoreOptions: '',
                selectedChatListMoreOptions_Options_cn: [
                    {label: '1) 导出/分享对话 (.json)', op: 'ExportChatData'},
                    {label: '2) 创建副本', op: 'CreateCopy'},
                    {label: '3) 清空记录 (保留最近单轮记录)', op: 'ClearPrevious'},
                    {label: '4) 清空记录', op: 'ClearAll'}
                ],
                selectedChatListMoreOptions_Options_en: [
                    {label: '1) Export & Share (.json)', op: 'ExportChatData'},
                    {label: '2) Creat a copy', op: 'CreateCopy'},
                    {label: '3) Clear the history (Keep the latest round)', op: 'ClearPrevious'},
                    {label: '4) Clear the history', op: 'ClearAll'}
                ],
                isChatListTransparent: false, //窗口是否透明


                //自动轮换API Key窗口 Auto Key Rotation
                isShowDialog_AutoKeyRotation: false,//是否显示对话列表窗口
                changeAutoKeyRotationSorted_Mode: 0, //视图的排序，简易的排序，切换不同的排序。 *不会影响轮询/轮换的排序
                isShowBulkAddPanel_AutoKeyRotation: false,//是否显示批量添加的面板


                //设置窗口 Settings
                isShowDialog_Settings: false, //是否显示设置窗口
                isShowProConfig: true, // 显示/隐藏 API专业设置 Show/Hide Professional API Settings
                isSimpleMode: false,  //true=简约模式（默认模式），false=新用户模式。区别在于简约模式显示的"提示信息"会少些。 true=Simple Mode (Normal Mode), false=User Onboarding. The difference is that there will be more "help information" in User Onboarding.
                simpleModeOptions_cn: [
                    {label: '默认模式', state: true},
                    {label: '新用户模式', state: false}
                ],
                simpleModeOptions_en: [
                    {label: 'Normal Mode', state: true},
                    {label: 'User Onboarding', state: false}
                ],


                // 已验证的API Key数据   *自动修改/记录 Auto modification/record  *非人工 Not manually
                // v7.18 如果您需要手动修改apikeyData的JSON文件进行批量添加API Key，只需要记录apikey一个属性即可，导入JSON文件，代码会自动添加其他属性，并填入默认值。
                // v7.18 If you need to manually modify the JSON file of apikeyData for bulk addition of API Keys, you only need to record the apikey attribute. Import the JSON file and the code will automatically add other attributes and fill in the default values.
                apiCheckedData: [{
                    apikey: '',	 //Not null，Default:''
                    apiurl: '',	 //Not null，Default:''  *v7.18 not used
                    date1: 0, //请求的时间1  时间戳 Recording request time. Not null，Int，Default:0
                    date2: 0, //请求的时间2  时间戳 Recording request time. Not null，Int，Default:0
                    date3: 0, //请求的时间3  时间戳 Recording request time. Not null，Int，Default:0
                    nexttime: 0, //*新增变量日期：7.10  预估下次可用的时间 时间戳 Estimated next available time. Not null，Int，Default:0
                    level: null, //目前当类别用，自动分类，自动标注类别。目前有 0、1、和 2、3、4、5 和 8、9 这几个。0代表付费key，1代表每分钟保证有3～5次的独享的免费key，2、3、4、5代表不稳定的多人共用的免费key，8代表无效，9代表配额不足/过期的key。  Allowing null，null or Int，Int:01234589，Default:null
                    freq: 0, //Not null，Number，Default:0  *v7.18 not used
                    vpn: '', //Not null，Default:''      *v7.18 not used
                    owner: 'me', //Not null，Default:''  *v7.18 not used
                    succcount: 0, //*新增变量日期：7.13  统计成功次数  Not null，Int，Default:0
                    failcount: 0, //*新增变量日期：7.13  统计失败次数  Not null，Int，Default:0
                    succrate: 0,  //*新增变量日期：7.13  成功率  Not null，Number，Default:0
                    updatetime: 0 //*新增变量日期：7.13  最后修改的时间  Not null，Int，Default:0
                }],
                lastCheckApiTime: 0, // 记录上一次验证API Key的时间，4秒内只能发送一次验证请求
                lastAPIKeyInputText: '',//记录最后一次使用的key
                lastAPIKeyInputText_Available: '',//记录最后一次可用的的key
                defaultAPIKey: '',//记录写在代码中的固定的APIKey（this.api），如果存在的话

                apitemperature: 0.7, //（0.0～2.0）接口参数 temperature 默认0.7 (官方默认1.0) v-model.number
                apiMaxTokens: 2048, //（1～4096～16384）接口参数 max_tokens 默认2048 v-model.number (4095 4096 4097 16384 16385 gpt-3.5 gpt-4 gpt3.5 gpt4)
                apiTopP: 1, // （0.0～1.0）接口参数 top_p 官方默认1 v-model.number
                apiPresencePenalty: 0, //（-2.0～2.0）接口参数 presence_penalty 官方默认0 v-model.number
                apiFrequencyPenalty: 0, //（-2.0～2.0）接口参数 frequency_penalty 官方默认0 v-model.number

                openSmartMaxTokens: true, // 智能max_tokens状态。通过估算自动调节。 Smart max_tokens
                smartMaxTokensOptions_cn: [
                    {label: '已启用 智能max_tokens', state: true},
                    {label: '已关闭 智能max_tokens', state: false}
                ],
                smartMaxTokensOptions_en: [
                    {label: 'Smart max_tokens Enabled', state: true},
                    {label: 'Smart max_tokens Disabled', state: false}
                ],
                isSendingNow_ForSmartMaxTokens: false, //是否正在发送消息 同isSendingNow。为了不改动isSendingNow的位置
                isFixingMaxTokens_SmartMaxTokens: false, //标记为“正在修复MaxTokens”时，本次不会执行修改max_tokens的动作
                isFirstRetry_SmartMaxTokens: true, //是否为首次自动重问
                isReminded_SmartMaxTokens: -1, //是否提醒过，提醒当前状态是关闭的。-1 不提醒 0 提醒  >0 不提醒

                apiGPTModel: 'gpt-3.5-turbo-16k', // 接口参数 model 默认'gpt-3.5-turbo-16k'

                //	<option v-for="option in gptModelOptions" :value="option.model">{{ option.label }}</option>
                //	gptModelOptions: [
                //	  { label: 'ChatGPT', model: 'gpt-3.5-turbo' },
                //	  { label: 'gpt-3.5-turbo-0301', model: 'gpt-3.5-turbo-0301' },
                //	  { label: 'gpt-3.5-turbo-0613', model: 'gpt-3.5-turbo-0613' },
                //	  { label: 'gpt-3.5-turbo-16k', model: 'gpt-3.5-turbo-16k' },
                //	  { label: 'gpt-3.5-turbo-16k-0613', model: 'gpt-3.5-turbo-16k-0613' }
                //	],
                // 以下填写的模型资料中 modelname 、 maxtokens 必须填写准确，与官方一致。如果要填写过期时间expiredtime，也必须与官方一致，格式为“2023-09-13T00:00:00”
                // The model information, "modelname" and "maxtokens", must be accurately filled and consistent with the official information.  The expiration date of the model, "expiredtime", is optional and should be filled in the format of "2023-09-13T00:00:00".
                // 可修改 Editable - GPT模型 GPT Model List   modelname:必填(Required)  maxtokens:必填(Required),Int  *注意数据类型、格式、及不要出现多余的空格。 *Please take note of the data type, format, and avoid any unnecessary spaces.
                gptModelList: [
                    {modelname: 'gpt-3.5-turbo-0301', alias: '', maxtokens: 4096, expiredtime: '2023-09-13T00:00:00'},
                    {modelname: 'gpt-3.5-turbo', alias: 'ChatGPT', maxtokens: 4096, expiredtime: ''},
                    {modelname: 'gpt-3.5-turbo-0613', alias: '', maxtokens: 4096, expiredtime: ''},
                    {modelname: 'gpt-3.5-turbo-16k', alias: '', maxtokens: 16384, expiredtime: ''},
                    {modelname: 'gpt-3.5-turbo-16k-0613', alias: '', maxtokens: 16384, expiredtime: ''}
                ],

                //仅支持/v1/chat/completions类型接口url。 接口与模型的关系 https://platform.openai.com/docs/models/model-endpoint-compatibility
                apiURL: 'https://api.openai.com/v1/chat/completions',  // 接口参数 接口网址 默认官方接口'https://api.openai.com/v1/chat/completions'
                inputApiURL: '',  //Input值
                selectedApiURL: '',  //selected值
                apiURLOptions_cn: [
                    {
                        label: '官方  • OpenAI官方API (GPT-3.5 / GPT-4) 需魔法 稳定 速度快',
                        url: 'https://api.openai.com/v1/chat/completions'
                    },
                    {
                        label: '非官方  • 第三方API 免魔法 7月仍可用',
                        url: 'https://api.openai-proxy.com/v1/chat/completions'
                    }
                ],
                apiURLOptions_en: [
                    {
                        label: "OpenAI  • OpenAI's official API. GPT-3.5 / GPT-4 API endpoint.",
                        url: 'https://api.openai.com/v1/chat/completions'
                    },
                    {
                        label: '3rd party  • Third-party APIs. Unofficial. 免魔法 7月仍可用',
                        url: 'https://api.openai-proxy.com/v1/chat/completions'
                    }
                ],

                //**常规设置第一排
                //移动设备页面比例  For mobile devices
                pageScaling: '0.8', //字符型，用于存储设置
                selectedPageScaling: '', //下拉框选项，选后后清空
                selectedPageScalingOptions: [], //0.5～1  超过100%，动态设置似乎没有效果

                //语言设置
                // * Modifying the default language in the code (change to English): Please search for "Default language" in the code for quick location.
                settingsLanguage: 'cn', //简体中文cn 英文en。 Chinese-Simplified = cn  English = en   * 可修改 Editable. Default language  * 主要在userVariables_default中修改 Make modifications primarily in userVariables_default.
                settingsLanguageOptions: [
                    {label: '中文', lang: 'cn'},
                    {label: 'English', lang: 'en'}
                ],
                //字体大小设置 默认14px
                settingsFontSize: 14, //数字 数值型
                settingsFontSizeOptions: [
                    {label: '9', fontsize: 9},
                    {label: '10', fontsize: 10},
                    {label: '11', fontsize: 11},
                    {label: '12', fontsize: 12},
                    {label: '13', fontsize: 13},
                    {label: '14', fontsize: 14},
                    {label: '15', fontsize: 15},
                    {label: '16', fontsize: 16},
                    {label: '17', fontsize: 17},
                    {label: '18', fontsize: 18},
                    {label: '19', fontsize: 19},
                    {label: '20', fontsize: 20},
                    {label: '21', fontsize: 21},
                    {label: '22', fontsize: 22},
                    {label: '23', fontsize: 23},
                    {label: '24', fontsize: 24},
                    {label: '25', fontsize: 25},
                    {label: '26', fontsize: 26},
                    {label: '27', fontsize: 27},
                    {label: '28', fontsize: 28},
                    {label: '29', fontsize: 29}
                ],
                //是否显示时间(微信样式) 默认显示
                settingsTime_WechatStyle: true,
                settingsTimeWechatStyleOptions_cn: [
                    {label: '显示', displaytime: true},
                    {label: '不显示', displaytime: false}
                ],
                settingsTimeWechatStyleOptions_en: [
                    {label: 'Show', displaytime: true},
                    {label: 'Hide', displaytime: false}
                ],
                //是否显示时间(消息) 默认显示
                settingsTime_Message: 2, // 数字，数值型 代表三种状态 state.  1:hide ; 2:show height:0 ; 3:show height:+10
                settingsTimeMessageOptions_cn: [
                    {label: '不显示', displaytime: 1},
                    {label: '显示', displaytime: 2},
                    {label: '显示(增大间距)', displaytime: 3}
                ],
                settingsTimeMessageOptions_en: [
                    {label: 'Hide', displaytime: 1},
                    {label: 'Show', displaytime: 2},
                    {label: 'Show(Increase Spacing)', displaytime: 3}
                ],
                //是否显示分割线 默认显示
                settingsDividerLine: true,
                settingsDividerLineOptions_cn: [
                    {label: '显示', displaydividerline: true},
                    {label: '不显示', displaydividerline: false}
                ],
                settingsDividerLineOptions_en: [
                    {label: 'Show', displaydividerline: true},
                    {label: 'Hide', displaydividerline: false}
                ],

                //**常规设置第二排
                //上下文模式 默认连续对话  待开发  目前只有：不限制  1. 0-不限制 ；2. 固定次数后自动清空 ； 3. 永远保留最后几条的记录
                settingsContextualMode: 0, // 数字，数值型 0代表连续对话，其他代表固定次数后会自动清空记忆，即 1 = 1*Q&A ; 2 = 2*Q&A *考虑如果只是次数到了自动清空，这个意义不是很大。最好是永远保留最后几次的上下文，这样的设置会更实用，能结合压缩上下文就更好了。这个功能待开发。。。
                settingsContextualModeOptions_cn: [
                    {label: '不限制', mode: 0}
                ],
                settingsContextualModeOptions_en: [
                    {label: 'No Limit', mode: 0}
                ],

                //是否开启验证API Key的快捷键 （电脑端使用） 默认开启
                settingsShortcut_CheckApiKey: true,
                settingsShortcutCheckApiKeyOptions_cn: [
                    {label: 'F2', shortcut: true},
                    {label: '无', shortcut: false}
                ],
                settingsShortcutCheckApiKeyOptions_en: [
                    {label: 'F2', shortcut: true},
                    {label: 'None', shortcut: false}
                ],

                //是否开启 清空 的快捷键 （电脑端使用） 默认关闭
                settingsShortcut_Clear: false,
                settingsShortcutClearOptions_cn: [
                    {label: 'F1', shortcut: true},
                    {label: '无', shortcut: false}
                ],
                settingsShortcutClearOptions_en: [
                    {label: 'F1', shortcut: true},
                    {label: 'None', shortcut: false}
                ],

                //是否开启 撤销 的快捷键 （电脑端使用） 默认关闭
                settingsShortcut_Undo: false,
                settingsShortcutUndoOptions_cn: [
                    {label: 'F3', shortcut: true},
                    {label: '无', shortcut: false}
                ],
                settingsShortcutUndoOptions_en: [
                    {label: 'F3', shortcut: true},
                    {label: 'None', shortcut: false}
                ],

                //是否开启 重问 的快捷键 （电脑端使用） 默认开启
                settingsShortcut_Retry: true,
                settingsShortcutRetryOptions_cn: [
                    {label: 'F4', shortcut: true},
                    {label: '无', shortcut: false}
                ],
                settingsShortcutRetryOptions_en: [
                    {label: 'F4', shortcut: true},
                    {label: 'None', shortcut: false}
                ],

                //**常规设置 头像  “Change-GPT-Green-SVG”是固定的，调用SVG的标识符。
                userAvatarURL: '/img/me-bili.jpg',  // *可修改 Editable. 默认的用户头像 Default User Avatar.
                inputUserImageUrl: '/img/me-bili.jpg', // *可修改 Editable. 默认用户头像的url地址，和上面的"userAvatarURL"保持一致。这个是出现在编辑框的文本内容。 Will display in the input. ("inputUserImageUrl" & "userAvatarURL") Two URLs are consistent.
                gptAvatarURL: '',   // 当前GPT头像网址
                inputGPTImageUrl: '',  // GPT input文本
                selectedImageUrl: '',  // 预设头像库下拉框值
                userpresetImages_cn: [
                    {name: '2) 预设: 哔哩哔哩头像', url: '/img/me-bili.jpg'},
                    {name: '3) 预设: 黑色GPT头像', url: '/img/favicon.ico'}
                ],
                userpresetImages_en: [
                    {name: '2) Pre-set: Bilibili Logo', url: '/img/me-bili.jpg'},
                    {name: '3) Pre-set: Black GPT Logo', url: '/img/favicon.ico'}
                ],
                gptpresetImages_cn: [
                    {name: '1) 预设: 绿色GPT头像', url: 'Change-GPT-Green-SVG'},
                    {name: '2) 预设: 黑色GPT头像', url: '/img/favicon.ico'} //黑色头像没有将绿色SVG改色，而是选用官网的ico图标
                ],
                gptpresetImages_en: [
                    {name: '1) Pre-set: Green GPT Logo', url: 'Change-GPT-Green-SVG'},
                    {name: '2) Pre-set: Black GPT Logo', url: '/img/favicon.ico'}
                ],
                isgptGreenSvgShow: true, //（显示<svg>的同时，隐藏<img>）或（隐藏<svg>时，显示<img>）。  (display<img> & hide<svg>) or (hide<img> & display<svg>)
                GPTGreenSVG_UrlText_cn: 'GPT-Green-SVG *无网址，仅通过[GPT头像]更新', // GPT绿色SVG头像对应的显示的文本 cn
                GPTGreenSVG_UrlText_en: 'GPT-Green-SVG *No URL, only updated through [GPT Avatar].', // GPT绿色SVG头像对应的显示的文本 en
                userAvatarUp_base64: '', //user - Upload avatar (<=350KB) base64
                userAvatarUp_blob: null, //user - Upload avatar (<=350KB) blob
                userAvatarUp_blobUrl: '', //user - Upload avatar (<=350KB) blob url

                //**常规设置 本地缓存
                selectedDeleteLocalStorageData: '',
                selectedDeleteLocalStorageDataOptions_cn: [
                    {label: '1) 一键删除所有数据', type: 'DeleteAllData'},
                    {label: '2) 删除所有[对话]数据', type: 'DeleteChatDataList'},
                    {label: '3) 删除所有[密钥 API Key]数据', type: 'DeleteAPIKeyData'},
                    {label: '4) 删除所有[提示词]数据', type: 'DeletePromptsData'},
                    {label: '5) 删除[上传的用户头像]', type: 'DeleteUploadedUserAvatar'},
                    {label: '6) 删除所有[设置]数据', type: 'DeleteSettingsData'}
                ],
                selectedDeleteLocalStorageDataOptions_en: [
                    {label: '1) Delete All Data with One Click', type: 'DeleteAllData'},
                    {label: '2) Delete All [Chat] Data', type: 'DeleteChatDataList'},
                    {label: '3) Delete All [API Key] Data', type: 'DeleteAPIKeyData'},
                    {label: '4) Delete All [Prompt Generator] Data', type: 'DeletePromptsData'},
                    {label: '5) Delete [Uploaded User Avatar]', type: 'DeleteUploadedUserAvatar'},
                    {label: '6) Delete All [Settings] Data', type: 'DeleteSettingsData'}
                ],
                selectedExportLocalStorageData: '',
                selectedExportLocalStorageDataOptions_cn: [
                    {label: '1) 一键导出所有数据', type: 'ExportAllData'},
                    {label: '2) 导出所有[对话]数据', type: 'ExportChatDataList'},
                    {label: '3) 导出所有[密钥 API Key]数据', type: 'ExportAPIKeyData'},
                    {label: '4) 导出所有[提示词]数据', type: 'ExportPromptsData'},
                    {label: '5) 导出所有[设置]数据', type: 'ExportSettingsData'}
                ],
                selectedExportLocalStorageDataOptions_en: [
                    {label: '1) Export All Data with One Click', type: 'ExportAllData'},
                    {label: '2) Export All [Chat] Data', type: 'ExportChatDataList'},
                    {label: '3) Export All [API Key] Data', type: 'ExportAPIKeyData'},
                    {label: '4) Export All [Prompt Generator] Data', type: 'ExportPromptsData'},
                    {label: '5) Export All [Settings] Data', type: 'ExportSettingsData'}
                ],


                isLocalStorageSupported: true, //当前浏览器是否支持localStorage
                isLocalStorageAllowed: true, //是否允许使用本地存储。
                islocalStorageFirstLoad: true, //是否首次读取 用途：1) 首次读取时，需要重新生成头像的blob url
                localStorage_lastSaveTime: null,// 记录最近一次自动或手动备份操作发生时的时间戳（毫秒）
                localStorage_lastSaveTime_onblur: null,// 记录最近一次自动或手动备份操作发生时的时间戳（毫秒）*onblur 当前页面失去焦点的事件，触发频率较高
                chatcreatedtime: null, //对话的创建时间 *唯一值
                chatlasttime: null, //最后一次操作的时间
                chatid: null,  //ID *可重复
                chattitle: '', //标题
                chatDataList: [], //对话列表，包含所有对话和对话的所有记录。 由“chatdata”组合成的数组
                selectedChatData: {}, //当前被选中的chat data，主要用于html绑定列表+通过chatcreatedtime查找对比，其他数据非实时更新
                localStorageEvent_chatDataList_update_start: false,//事件状态

                //导出
                chatHistoryPromptOnlyUseBackticks: false, //导出的聊天记录中，系统提示词的markdown语法处理方式。 true:仅用"代码块"包裹  false:自动判断使用"引用"或者"代码块"包裹
                exportTXTFileName: "LinGPT_ChatHistory", //导出全部历史记录的文件名
                exportTXTFileName_SingleRound: "LinGPT_ChatHistory_SingleRound", //导出单轮历史记录的文件名
                exportJSONFileName: "LinGPT Data_",
                selectedExportChatHistory: '',
                selectedExportChatHistoryOptions_cn: [
                    {label: '1) 导出聊天记录·单轮记忆 (.md)', type: 'ChatHistory_Single_MD'},
                    {label: '2) 导出聊天记录·单轮记忆 (.txt)', type: 'ChatHistory_Single_TXT'},
                    {label: '3) 导出聊天记录/日志·全部 (.md)', type: 'ChatHistory_ALL_MD'},
                    {label: '4) 导出聊天记录/日志·全部 (.txt)', type: 'ChatHistory_ALL_TXT'}
                ],
                selectedExportChatHistoryOptions_en: [
                    {label: '1) Export Chat History for a Single Round (.md)', type: 'ChatHistory_Single_MD'},
                    {label: '2) Export Chat History for a Single Round (.txt)', type: 'ChatHistory_Single_TXT'},
                    {label: '3) Export All Chat History / Logs (.md)', type: 'ChatHistory_ALL_MD'},
                    {label: '4) Export All Chat History / Logs (.txt)', type: 'ChatHistory_ALL_TXT'}
                ],


                //消息通知 tooltips弹窗 Notification tooltips
                isShowNotification: false, //是否显示通知 Show
                setNotificationContent: '', //通知的内容 Message
                isNotificationHasMask: false, //是否显示遮罩 Mask
                isNotificationBottomShowCloseBtn: false,//bottom底部通知时，是否显示关闭按钮
                setNotificationPosition: 'center', //通知样式 center中心 or bottom底部
                setNotificationTitle: '', //标题文字  Title 通知 信息 警告 提醒 错误 ...
                notificationTimeoutId: 0, // 上一个通知的定时器id

                // v7.05 开始提示词不建议写在代码中。Starting from v7.05, it is advised not to write system prompts in the code.
                // 系统提示词。不填的话，gptSystemPrompt后面单引号内留空{ gptSystemPrompt: '', }，填写后，会出现在“提示词编辑器中”，不会自动生效，需要手动保存后才会生效。
                // 下方这行设置gptSystemPrompt 是设置[默认的指令/提示词/人设]的第一种方法，指令写在单引号内。 如果默认指令写在这里，这个指令会显示在聊天界面的指令输入框中。 如果还设置有第二种隐形指令的话，以隐形指令为准，第一种会被忽略。
                // 保存到上下文数组msgContent中才能生效，不保存则不生效。
                // System Prompt(optional): Will be displayed in the "Prompt Editor". It needs to be saved before it takes effect.
                gptSystemPrompt: '', //v7.05 该变量是“提示词编辑器的内容”，不再是系统提示词。The variable is now "the content of the prompt editor" instead of the system prompt.

                //可修改 Editable - (一般不改，很少会用到隐形指令) 不填的话，单引号内留空，即： gptSystemPrompt_hidden: '',
                // 下方这行设置gptSystemPrompt_hidden 是设置[默认的指令/提示词/人设]的第二种方法，指令写在单引号内。 第二种方法是[隐形指令/隐藏指令/隐形提示词/隐藏提示词]的写法。如果默认指令写在这里，这个指令不会显示在聊天界面的指令输入框中，指令是隐形的。 在本代码中搜索“隐形指令”可查看[添加 disabled 来禁止聊天界面上输入新指令]的方法。
                // 如果用第二种方法设置了[隐形指令]，那么GPT指令将以这里的[隐形指令]为准，会自动忽略聊天界面上的其他指令（包括空白的指令），不会叠加。
                // 设置隐形提示词后，提示词弹窗将被禁用，代码写在mounted()里
                // Hidden System Prompt, with the highest priority. Once set, the System Prompt will be based on the Hidden System Prompt, and the Prompt in the System Prompt input box will be invalid.
                // If "gptSystemPrompt_hidden" is set, the Prompt Generator will be disabled. The judgment code is written in the mounted() method.
                gptSystemPrompt_hidden: '',


                //v-html
                msgList: [{
                    msg: "",   //默认空。这里不填内容！ Do not fill in!
                    my: false,  // 是否是我发送消息，用作判断消息左右显示
                    prompt: false, // 是否是系统消息
                    datetime: "2000-01-01 00:00:00", // 默认"2000-01-01 00:00:00"，用于判断，不要改
                    nickName: "",
                    wechatstyledatetime: null,
                    hr: false
                }],

                //msgContent：上下文 context
                msgContent: [{role: "system", content: ""}],

                //msgTokens：对应msgContent记录每条消息的token数量 Synchronize updates with 'msgContent' and record tokens data for each message.  role: AI & user.
                //completion_tokens：单条消息的tokens数量 Tokens for a single message.
                //total_tokens：截止当前消息的tokens总数 Current total tokens.
                //永远保留第一条记录[0]，对应msgContent[0]
                //role: AI & user.
                msgTokens: [{role: "AI", completion_tokens: 0, total_tokens: 0}], //tokens:int

                msg: "", //输入的消息 message  聊天输入框 chat input box
                msgContentForMsgList_SingleRound: [{role: "system", content: "", datetime: "2000-01-01 00:00:00"}], //（默认时间不要改） 与msgContent的不同点：1.会保留未撤销的发送失败的数据；2.数据与msgList的最后一轮对话对应；3.记录时间。 Differences from msgContent: 1. Failed messages that have not been undone will be retained; 2. Data corresponds to msgList (New Chat, Only single round); 3.Add datetime。


                userSettings_Version: '1.7.09',
                //  User用户 Settings - 变量名单/变量列表/变量白名单/默认值。  Related to User - Variable list, variable whitelist, default value.
                //* 可修改 Editable.  默认的设置，默认参数，默认值。 Default settings, default parameters, default values.
                //* 可修改 Editable.  默认值主要在这里修改 Modify default values primarily here.
                //* 支持浏览器本地存储(localStorage)，直接在网页中修改即可，下次再打开时会加载上次的变量设置。
                //* Supports for browser local storage (localStorage), which can be directly modified in the [Settings] of the webpage.
                userVariables_default: {
                    apitemperature: 0.7, //（0.0～2.0）接口参数 temperature 默认0.7 (官方默认1.0) v-model.number
                    apiMaxTokens: 2048, //（1～4096～16384）接口参数 max_tokens 默认2048 v-model.number (4095 4096 4097 16384 16385 gpt-3.5 gpt-4 gpt3.5 gpt4)
                    apiGPTModel: 'gpt-3.5-turbo-16k', // 接口参数 model 默认'gpt-3.5-turbo-16k'
                    apiTopP: 1, // （0.0～1.0）接口参数 top_p 官方默认1 v-model.number
                    apiPresencePenalty: 0, //（-2.0～2.0）接口参数 presence_penalty 官方默认0 v-model.number
                    apiFrequencyPenalty: 0, //（-2.0～2.0）接口参数 frequency_penalty 官方默认0 v-model.number
                    apiURL: 'https://api.openai.com/v1/chat/completions',  // 接口参数 接口网址 默认官方接口'https://api.openai.com/v1/chat/completions'
                    selectedApiURL: '',  //selected值

                    settingsLanguage: 'cn', //可修改 Editable. 默认语言 简体中文cn 英文en。 可修改 Editable. Default language. Chinese-Simplified = cn  English = en
                    settingsFontSize: 14, //字体大小设置 默认14px 数字 数值型
                    settingsTime_WechatStyle: true, //是否显示时间(微信样式) 默认显示
                    settingsTime_Message: 2, //是否显示时间(消息) 默认显示 数字，数值型 代表三种状态 state.  1:hide ; 2:show height:0 ; 3:show height:+10
                    settingsDividerLine: true, //是否显示分割线 默认显示

                    settingsShortcut_CheckApiKey: true, //是否开启验证API Key的快捷键 （电脑端使用） 默认开启
                    settingsShortcut_Clear: false, //是否开启 清空 的快捷键 （电脑端使用） 默认关闭
                    settingsShortcut_Undo: false, //是否开启 撤销 的快捷键 （电脑端使用） 默认关闭
                    settingsShortcut_Retry: true, //是否开启 重问 的快捷键 （电脑端使用） 默认开启

                    chatHistoryPromptOnlyUseBackticks: false, //导出的聊天记录中，系统提示词的markdown语法处理方式。 true:仅用"代码块"包裹  false:自动判断使用"引用"或者"代码块"包裹
                    isShowProConfig: true, // 显示/隐藏 API专业设置 Show/Hide Professional API Settings
                    isSimpleMode: false,  //true=简约模式（默认模式），false=新用户模式。区别在于简约模式显示的"提示信息"会少些。 true=Simple Mode (Normal Mode), false=User Onboarding. The difference is that there will be more "help information" in User Onboarding.
                    pageScaling: '0.8', //*新增变量日期：7.09 。 移动设备页面比例
                },

                chatDataList_Version: '1.7.05',
                //  聊天相关 Chat Variables - 变量名单/变量列表/变量白名单/默认值。  Related to Chat Variables  - Variable list, variable whitelist, default value.
                // 不改动 Non-editable
                chatVariables_default: {
                    btnDisabledState_Export: true,
                    settingsContextualMode: 0, //上下文模式 默认连续对话  待开发。  数字，数值型 0代表连续对话，其他代表固定次数后会自动清空记忆，即 1 = 1*Q&A ; 2 = 2*Q&A *考虑如果只是次数到了自动清空，这个意义不是很大。最好是永远保留最后几次的上下文，这样的设置会更实用，能结合压缩上下文就更好了。这个功能待开发。。。
                    hasSystemPromptBeenSaved: false,
                    userMsgTokensForRetry: 0, //int 临时记录重问时的tokens数据
                    succQA_Count: 0, //int 当前有效的上下文数量，一问一答succQA_Count = 2 。 数量变化对应this.msgContent
                    totaltokens: 0, //int
                    chathistory: '# GPT聊天记录 / Chat History  \r\n\r\n',
                    gptSystemPromptReadOnly: '',//仅展示
                },

                //  聊天相关 Chat Data - 变量名单/变量列表/变量白名单/默认值。  Related to Chat Data - Variable list, variable whitelist, default value.
                // 不改动 Non-editable
                chatData_default: {
                    //*单独手动加入 version: '******',  *需要排除
                    chatcreatedtime: null,
                    chatlasttime: null,
                    chatid: null,
                    chattitle: '',

                    msgList: [{
                        msg: "",
                        my: false,  // 是否是我发送消息，用作判断消息左右显示
                        prompt: false, // 是否是系统消息
                        datetime: "2000-01-01 00:00:00",
                        nickName: "",
                        wechatstyledatetime: null,
                        hr: false
                    }],
                    msgContent: [{role: "system", content: ""}],
                    msgTokens: [{role: "AI", completion_tokens: 0, total_tokens: 0}],
                    msgContentForMsgList_SingleRound: [{role: "system", content: "", datetime: "2000-01-01 00:00:00"}],
                    msg: '',
                    //*手动加入 chatVariables  *Chat Variables 需要排除
                },

                apikeyData_Version: '1.7.13',
                //  API Key数据 API Keys Data - 变量名单/变量列表/变量白名单/默认值。  Related to API key - Variable list, variable whitelist, default value.
                // 不改动 Non-editable
                apikeyData_default: {
                    api: '', //不存数据，仅用于重置空值，及作为白名单使用。 Do not store data, only use for resetting empty values and as a whitelist.  **原先初始值等于thia.api, 写在mounted()，但，从v7.22开始，初始值保持为空。
                    timerId: null,
                    apiCheckedData: [], //数组。不存数据，仅用于重置空值，及作为白名单使用。 Do not store data, only use for resetting empty values and as a whitelist.  **原先默认等于thia.apiCheckedData, 写在mounted()，但，从v7.22开始，初始值保持为空。
                },

                promptsData_Version: '1.7.05',
                //  提示词数据 Prompts Data - 变量名单/变量列表/变量白名单/默认值。  Related to Prompts - Variable list, variable whitelist, default value.
                // 不改动 Non-editable
                promptsData_default: {
                    gptSystemPrompt: '', //不要改，初始值等于thia.gptSystemPrompt, 写在mounted()  *7.22 待定
                    gptPrompts: [], //数组 不存数据，仅用于重置空值，及作为白名单使用。 Do not store data, only use for resetting empty values and as a whitelist.  *原先设置初始值等于thia.gptPrompts, 写在mounted()，但，从v7.22开始，初始值保持为空。
                }
            }
        },

        methods: {

            initWebSocket() {
                debugger;

                // 创建websocket连接
                this.createWebSocket();

                // 建立websocket连接之后需要做的事
                this.webSocket.onopen = this.onopenWebSocket;

                // 接收服务端消息
                this.webSocket.onmessage = this.onMessageWebSocket;

                // websocket连接发送错误后执行方法
                this.webSocket.onerror = this.onerrorWebSocket;

                //注册监听事件
                window.addEventListener('onmessageWS', this.getSocketData);

            },

            // 创建websocket连接
            createWebSocket() {
                // 连接websocket
                const userId = Cookies.get('chat_token');
                this.webSocket = new WebSocket(this.wsUrl + userId);
            },

            // 建立websocket连接之后需要做的事
            onopenWebSocket() {
                console.log("建立websocket");
                this.heartbeatWebsocket();
            },

            // websocket连接发送错误后执行方法
            async onerrorWebSocket() {
                console.log("websocket连接发送错误后执行方法")
                this.reconnectWebsocket();
            },

            // 监听接收到websocket消息
            async onMessageWebSocket(evt) {

                // 重置心跳包
                this.heartbeatWebsocketRestart();

                window.dispatchEvent(new CustomEvent('onmessageWS', {
                    detail: {
                        data: evt.data
                    }
                }))
            },

            // 心跳检测
            heartbeatWebsocket() {
                debugger;
                const that = this;
                that.reconnectTimeoutObj && clearTimeout(that.reconnectTimeoutObj);
                that.serviceTimeoutObj && clearTimeout(that.serviceTimeoutObj);
                that.reconnectTimeoutObj = setTimeout(() => {

                    if (that.webSocket.readyState === 1) {
                        // 发送消息
                        that.webSocket.send("ping");
                    } else {
                        // 重连
                        that.reconnectWebsocket();
                    }
                    // // 如果超过一定时间还没重置，说明后端主动断开了
                    that.serviceTimeoutObj = setTimeout(() => {
                        that.webSocket.close();
                    }, that.reconnectTime)

                }, that.reconnectTime)
            },

            // 重连
            reconnectWebsocket() {

                // 如果已经连接，结束
                if (this.lockReconnect) {
                    return;
                }
                this.lockReconnect = true;
                this.timeoutNum && clearTimeout(this.timeoutNum);
                this.timeoutNum = setTimeout(() => {
                    this.initWebSocket();
                    this.lockReconnect = false;
                }, 5000)

            },

            // 重置心跳包
            heartbeatWebsocketRestart() {
                // 重置心跳
                this.reconnectTimeoutObj && clearTimeout(this.reconnectTimeoutObj);
                this.serviceTimeoutObj && clearTimeout(this.serviceTimeoutObj);
                this.heartbeatWebsocket();
            },


            // 收到消息处理
            getSocketData(res) {
                debugger;
                console.log(this.msgList)

                const resultMsg = res.detail.data;
                console.log(resultMsg, "resultMsg")
                if (resultMsg === 'pong') {  //心跳消息

                } else if (resultMsg.indexOf("webOnlineUserNum:") == 0) {   // 在线用户数量
                    this.onlineUserNum = resultMsg.replace("webOnlineUserNum:", "");
                } else {
                    let serviceData = JSON.parse(resultMsg);
                    // ...业务处理
                    this.isSendingNow = false; //更新发送状态
                    this.isSendingNow_ForSmartMaxTokens = false; //同步更新发送状态
                    this.endLoadingAnimation();
                    document.querySelector('.inputapikey').readOnly = false; //发送结束，apikey恢复可改
                    this.isFixingMaxTokens_SmartMaxTokens = false; //取消“正在修复MaxTokens”的状态（当Smart Tokens遇到错误，会自动修复并自动重问(Retry)，当重问成功后，需要重置状态）
                    this.lastAPIKeyInputText_Available = this.api;//更新最后一次输入框中的key，成功的
                    this.autoRetry_apiKeyExcludeList = []; //重置autoRetry数据-1: 清空"自动重问"的 API Key排除列表
                    this.autoRetry_FirstTime = 0; //重置autoRetry数据-2: 清空首次自动重问的时间
                    this.autoRetry_log = ''; //重置autoRetry数据-3: 清空临时日志
                    const currentTime = new Date().getTime(); // 获取当前时间

                    let aiReplyMsg = serviceData.msg; //v5.16 使用marked后，不再需要去掉头尾的换行符
                    let prompt = serviceData.prompt;    // 是否系统消息， true 系统消息
                    let nickName = serviceData.nickName;    // 是否系统消息， true 系统消息
                    let aiMsgMarkdown = marked.parse(aiReplyMsg); // marked.js + highlight.js  Markdown+高亮处理
                    if (aiReplyMsg.trim() == "" || aiReplyMsg.replace(/^\n|\n$/g, "").trim() == "") {
                        aiMsgMarkdown = "<p> </p>";  // v5.16 BUG B ： AI回复为空，则输出为加了<p>标签的空格，保持正确的气泡框样式
                    } else if (aiMsgMarkdown.trim() == "") {
                        aiMsgMarkdown = "<p>" + aiReplyMsg + "</p>"; //v5.16 BUG A ： AI回复不为空，但marked处理后变成空，则aiMsgMarkdown重置为未经过marked处理的原文
                    }
                    if (aiMsgMarkdown.trim() == "") {
                        aiMsgMarkdown = "<p> </p>"; // v5.16 BUG B ： 若AI回复为空，则输出加了<p>标签的空格，保持正确的气泡框样式
                    }

                    const aiMsgDatetimeNow = this.formatDateYYYYMMDDHHmmss(new Date(), ':');

                    this.msgList.push({
                        "msg": aiReplyMsg, // 消息内容
                        "my": false,     // 是否是我发送消息，用作判断消息左右显示
                        "prompt": prompt,     // 是否是系统消息
                        "datetime": aiMsgDatetimeNow,
                        "nickName": nickName,
                    })

                    //渲染 aimsg
                    this.$nextTick(() => {
                        debugger;
                        const aiDivs = document.querySelectorAll('div.aimsg');
                        const lastAIDiv = aiDivs[aiDivs.length - 1];
                        //lastAIDiv.innerHTML = aiMsgMarkdown; 6.19:改为v-html后，不再需要使用innerHTML

                        //滚动到最底部
                        this.scrollToBottomView();

                        //搜索div内容，找到符合条件的代码块，添加一键复制按钮。 addCopyButton.  Copying code to the clipboard
                        this.addCopyButtonToPreTags(lastAIDiv);

                        //一键复制按钮。为聊天气泡框添加一键复制按钮。 addCopyButton.  Copying Chat Message to the clipboard
                        this.addCopyButtonToChatMessage(lastAIDiv.parentElement);

                    });

                    // this.msgList.push this.msgContentForMsgList_SingleRound.push
                    // this.msgContentForMsgList_SingleRound.push({"role": res.data.choices[0].message.role , "content": aiReplyMsg , "datetime": aiMsgDatetimeNow }); //0531发现之前写错成'userMsgDatetimeNow'，暂未使用此数据

                    // res.data.choices[0].message.role = "assistant"
                    // if(this.msgContent.length < 2){ alert(" 找到一个bug\r\nA new bug has appeared.\r\n\r\n发送成功后 && PUSH之前，this.msgContent.length < 2，此时应该至少有两条，1-系统指令 2-提问的问题。  \r\n\r\nthis.msgContent.length：" + this.msgContent.length  ); }
                    // this.msgContent.push({"role": res.data.choices[0].message.role , "content": aiReplyMsg });
                    this.succQA_Count = this.msgContent.length - 1; //int. 更新
                    // # Don't "this.msgTokens.push" for now
                    //    restokens  == res.data.usage.total_tokens ：AI回复后的token总数
                    //    res.data.usage.prompt_tokens ：AI回复前的token总数，即user提问后的token总数
                    //    res.data.usage.completion_tokens ：AI单条内容的token数
                    //  1.更新user数据 Update user data
                    // this.msgTokens[this.msgTokens.length - 1]["total_tokens"] = res.data.usage.prompt_tokens ;
                    //  2.更新user数据 Update user data
                    // if(this.msgTokens.length < 2){ alert(" 找到一个bug\r\nA new bug has appeared.\r\n\r\n发送成功后 && PUSH之前，this.msgTokens.length < 2。  \r\n\r\nthis.msgTokens.length：" + this.msgTokens.length  ); }
                    // let aiPrevMessage_total_tokens = this.msgTokens[this.msgTokens.length - 2]["total_tokens"]; // length-2: Data of the AI's previous message
                    // this.msgTokens[this.msgTokens.length - 1]["completion_tokens"] = res.data.usage.prompt_tokens - aiPrevMessage_total_tokens ; //int.  user单条内容的token数
                    //console.log("实际的 精准的（  ，含+13/+10） send msg tokens : " + ( res.data.usage.prompt_tokens - aiPrevMessage_total_tokens ));
                    // # Can use 'this.msgTokens.push' now
                    //  3.新增AI数据 Add new AI data
                    // this.msgTokens.push({"role": "AI", "completion_tokens": res.data.usage.completion_tokens , "total_tokens": restokens });
                    // this.totaltokens = restokens;

                    let txt_lang2;
                    if (this.gptSystemPrompt_hidden.trim() != "") {
                        //若存在隐形的System设置，在聊天记录中做个标记
                        txt_lang2 = this.settingsLanguage == 'cn' ? '**GPT::Hidden** _' : '**GPT::Hidden** _';
                    } else {
                        txt_lang2 = this.settingsLanguage == 'cn' ? '**GPT** _' : '**GPT** _';
                    }
                    this.chathistory += (txt_lang2 + aiMsgDatetimeNow + '_  \r\n' + aiReplyMsg.replace(/\n/g, "\r\n") + '\r\n\r\n');

                    this.btnDisabledState_Sending = false;
                    this.btnDisabledState_Clear = false;
                    this.btnDisabledState_Undo = false;
                    this.btnDisabledState_ApiURL = false;
                    this.btnDisabledState_Language = false;
                    this.btnDisabledState_ContextualMode = false;

                    this.btnDisabledState_Export = false;
                    this.sentext = this.settingsLanguage == 'cn' ? '发送' : 'Send';
                    this.userMsgTokensForRetry = 0;


                    //更新 保存 聊天数据 chatData chatDataList
                    //更新最后活动时间
                    this.updateChatLastTime(aiMsgDatetimeNow);//最后活动时间

                    //更新this.apiCheckedData  记录已验证的API Key信息
                    // this.updateApiCheckedData(this.api, currentTime, 'success');
                }

            },


            //按钮：导出所有对话 Export chat history  filetype:'markdown'(.md) or 'plain'(.txt)
            ExportData(filecontent, filename, filetype) {
                // 创建Blob对象并生成下载链接
                const blob = new Blob([filecontent], {type: 'text/' + filetype});
                const urlObject = window.URL || window.webkitURL || window;
                const url = urlObject.createObjectURL(blob);
                // 创建下载链接并自动下载
                const downloadLink = document.createElement('a');
                downloadLink.href = url;
                downloadLink.style.display = "none";
                const now = new Date();
                filename = filename + ' ' + this.formatDateYYYYMMDDHHmmss(now, '.').slice(5); // like “10-14 10.23.45”
                filename = filetype == 'markdown' ? filename + '.md' : filename + '.txt';
                downloadLink.download = filename;
                document.body.appendChild(downloadLink);
                downloadLink.click();
                document.body.removeChild(downloadLink);
            },
            //按钮：导出最后一轮  showtime是否导出时间  exportprompt是否导出提示词  onlyusebackticks提示词是否只使用反引号包裹  filetype:'markdown'(.md) or 'plain'(.txt)
            ExportData_SingleRound(showtime, exportprompt, onlyusebackticks, filetype) {

                if (this.checkBusyStatus()) {
                    // 如果返回 true，正在发送或正在验证
                    return;
                }
                //发送中的状态不允许导出，所以当上下文this.msgContent的长度>=3时，表示存在有效问答（单轮）
                if (this.msgContent.length < 3) {
                    //不存在发送成功的聊天记录
                    let notif = this.settingsLanguage == 'cn' ? '导出本轮上下文记忆：当前不存在有效的聊天记录(单轮)，无数据可导出' : "Export current round's context: No valid context records found, no data available for export."; //存在单引号
                    setTimeout(() => { //之前有一个弹窗通知，这里需要延迟
                        this.showNotification(notif, 'bottom', '', 7000);
                    }, 1800);
                    return;
                }
                let context = this.msgContentForMsgList_SingleRound;
                let txt = '';
                let time = '';
                txt = this.settingsLanguage == 'cn' ? '# GPT聊天记录\r\n\r\n' : '# Chat History\r\n\r\n';

                //如果需要导出提示词， 对提示词用markdown语法进行包裹， 语法自动选择用"引用"或者"代码块",
                //参数onlyusebackticks可以指定用代码块（导出后，若提示词很复杂导致了排版错乱，那么就可以设置onlyusebackticks为true）
                if (exportprompt) {
                    const sys_prompt = context[0]['content'].trim();  //context[0]与this.msgList[0]相同，同步更新。 context[0]['role'] = "system"

                    //以下代码注释 详见addSystemPromptToChatHistory
                    let hasMultipleLines = /\r?\n\r?\n/.test(sys_prompt);
                    let backticks = this.findMaxBackticks(sys_prompt);
                    hasMultipleLines = backticks != '' ? true : hasMultipleLines;
                    backticks = backticks == '' ? '````' : backticks;
                    if (onlyusebackticks) {
                        hasMultipleLines = true;
                    }

                    let addMarkdownSyntaxStart = hasMultipleLines ? ('\r\n' + backticks + '\r\n') : '\r\n> ';  // (> 后面有个空格)
                    let addMarkdownSyntaxEnd = hasMultipleLines ? ('\r\n' + backticks + '\r\n\r\n') : '\r\n\r\n';
                    let nullprompt = '';  //提示词为空的情况
                    if (sys_prompt == '') {
                        nullprompt = this.settingsLanguage == 'cn' ? ': 空' : ': None';
                        addMarkdownSyntaxStart = '\r\n';
                        addMarkdownSyntaxEnd = '\r\n';
                    }

                    const head = this.settingsLanguage == 'cn' ? '#### 系统提示词 system prompt ' : '#### #System Prompt ';
                    txt += (head + nullprompt + addMarkdownSyntaxStart + sys_prompt + addMarkdownSyntaxEnd);
                }

                //开始读取对话记录
                for (let i = 1; i < context.length; i++) {
                    if (context[i]['role'] === 'user') {
                        if (i === context.length - 1) {
                            break; //最后一条记录，如果是user，说明是失败的，及没有[i+1],需要跳出循环
                        }
                        //排除掉发送失败的消息。判断下一条记录的role：发送失败的记录，下一条的role一定还是'user'；发送成功的记录，下一条的role一定是'assistant'。
                        let nextRole = context[i + 1]['role'];
                        while (nextRole === 'user' && i + 1 < context.length - 1) {
                            i++;
                            nextRole = context[i + 1]['role'];
                        }
                        if (nextRole === 'assistant') {
                            //此时i是发送成功的user的消息
                            time = showtime ? (' _' + context[i]['datetime'] + '_  \r\n') : ' \r\n';
                            txt += this.settingsLanguage == 'cn' ? '**User**' : '**User**';
                            txt += time;
                            txt += context[i]['content'].replace(/\n/g, "\r\n") + '\r\n\r\n';
                        }
                    } else if (context[i]['role'] === 'assistant') {
                        //gpt的回复
                        time = showtime ? (' _' + context[i]['datetime'] + '_  \r\n') : ' \r\n';
                        txt += this.settingsLanguage == 'cn' ? '**GPT**' : '**GPT**';
                        txt += time;
                        txt += context[i]['content'].replace(/\n/g, "\r\n") + '\r\n\r\n';
                    }
                }

                //导出 .md 或者 .txt
                this.ExportData(txt, this.exportTXTFileName_SingleRound, filetype);
            },

            //按钮：导出数据 JSON
            ExportData_JSON(stringData, filename) {
                //计算大小
                let filename_dataSize = '';
                try {
                    const _dataSize = this.calculateStringSize(stringData, 'utf-16') / 1024 / 1024;
                    let notif = '';
                    if (_dataSize > 4.99) {
                        notif = this.settingsLanguage == 'cn' ? '当前导出的数据已达到 ' + _dataSize.toFixed(2) + ' MB，可能无法在一些浏览器中导入 (部分浏览器本地存储上限为5MB)。如果需要适配更多的浏览器，建议分类导出 (或拆分为单个对话导出、抹掉日志)，以减小单个文件的大小。' : 'The currently exported data has reached ' + _dataSize.toFixed(2) + ' MB, which may exceed the local storage limit of 5MB for some browsers, causing import failure. If you need to adapt to more browsers, it is recommended to export data by categories to reduce the size of individual files or split them into separate chat exports, or erase logs.';
                        setTimeout(() => {
                            this.showNotification(notif, 'center', '');
                        }, 1600);//延迟 等首个通知弹出后 再替换
                    } else if (_dataSize > 3) {
                        notif = this.settingsLanguage == 'cn' ? '当前导出的数据大小为 ' + _dataSize.toFixed(2) + ' MB。小贴士：可以通过拆分对话或抹掉日志来缩小文件' : 'The current exported data size is ' + _dataSize.toFixed(2) + ' MB. Tip: You can reduce the file size by exporting individual chats or erasing logs.';
                        setTimeout(() => {
                            this.showNotification(notif, 'bottom', '', 8000);
                        }, 1600);//延迟 等首个通知弹出后 再替换
                    }
                    filename_dataSize = _dataSize.toFixed(2); //单位：MB
                    filename_dataSize = '(' + filename_dataSize + 'MB) ';
                } catch (e) {
                    filename_dataSize = '';
                    console.error('ExportData_JSON() - 计算大小出错，不影响导出', e);
                }

                // 创建Blob对象并生成下载链接
                const blob = new Blob([stringData], {type: 'application/json'});
                const url = URL.createObjectURL(blob);

                // 创建下载链接并自动下载
                const downloadLink = document.createElement('a');
                downloadLink.href = url;
                downloadLink.style.display = "none";
                const now = new Date();
                filename = filename + ' ' + filename_dataSize + this.formatDateYYYYMMDDHHmmss(now, '.').slice(5) + '.json'; // like “10-14 10.23.45”
                downloadLink.download = filename;
                document.body.appendChild(downloadLink);
                downloadLink.click();
                document.body.removeChild(downloadLink);
            },


            //格式化时间  date时间 delimiter分隔符  分隔符为":"输出：2021-10-14 10:23:45  分隔符为"."输出：2021-10-14 10.23.45
            formatDateYYYYMMDDHHmmss(date, delimiter) {
                const year = date.getFullYear();
                let month, day, hour, minute, second;
                if (String.prototype.padStart) {
                    month = (date.getMonth() + 1).toString().padStart(2, '0');
                    day = date.getDate().toString().padStart(2, '0');
                    hour = date.getHours().toString().padStart(2, '0');
                    minute = date.getMinutes().toString().padStart(2, '0');
                    second = date.getSeconds().toString().padStart(2, '0');
                } else {
                    month = (date.getMonth() + 1).toString();
                    day = date.getDate().toString();
                    hour = date.getHours().toString();
                    minute = date.getMinutes().toString();
                    second = date.getSeconds().toString();
                    // 在月份、日期、小时、分钟和秒数前补零
                    if (month.length === 1) {
                        month = '0' + month;
                    }
                    if (day.length === 1) {
                        day = '0' + day;
                    }
                    if (hour.length === 1) {
                        hour = '0' + hour;
                    }
                    if (minute.length === 1) {
                        minute = '0' + minute;
                    }
                    if (second.length === 1) {
                        second = '0' + second;
                    }
                }
                //ES6的写法：  return `${year}-${month}-${day} ${hour}${delimiter}${minute}${delimiter}${second}`;
                return year + '-' + month + '-' + day + ' ' + hour + delimiter + minute + delimiter + second; //兼容性更好。 Support more browsers.
            },
            //（6.20:改为DOM渲染之前判断）显示微信样式的时间。 判断时间间隔，跨度超过5分钟，返回当前时间戳，否则返回null。
            displayDatetime_WeChatStyle() {
                //直接获取倒数第1条记录的时间，无论是user还是ai。因为是在DOM渲染之前，所以是（length - 1）。最低为[0]
                //BUG日记：ios/macos, safari浏览器对new Date()存在兼容问题，safari不认"yyyy-MM-dd HH:mm:ss"格式的日期字符串，不识别“-”。
                let lastmsgdatetime = this.msgList[this.msgList.length - 1]["datetime"];
                lastmsgdatetime = lastmsgdatetime.replace(/-/g, "/");//转格式为yyyy/MM/dd HH:mm:ss ，兼容apple产品
                let lasttimestamp = new Date(lastmsgdatetime).getTime() / 1000; //获取指定时间lastmsgdatetime的时间戳，单位秒(s)
                let nowtimestamp = new Date().getTime() / 1000; //获取当前时间戳，单位秒(s)

                let min = (nowtimestamp - lasttimestamp) / 60;
                if (min > 5) {
                    return nowtimestamp;
                } else {
                    return null;
                }
            },

            // 显示消息通知 通知提示框 tooltips	两种样式：position center or bottom
            showNotification(content, position, title, bottomshowtime) {

                if (this.isShowNotification && this.notificationTimeoutId != -1) {
                    clearTimeout(this.notificationTimeoutId);
                    this.isShowNotification = false;
                    this.notificationTimeoutId = -1;
                }

                //整体延时0.05秒 解决很多问题
                setTimeout(() => {
                    this.setNotificationContent = content;
                    this.setNotificationPosition = position;//其实可以用isNotificationHasMask直接代替setNotificationPosition
                    this.isNotificationHasMask = position == 'center' ? true : false;
                    this.notificationTimeoutId = position == 'center' ? -1 : this.notificationTimeoutId;
                    if (title != null) {
                        this.setNotificationTitle = title.trim();
                    }
                    const notificationWrapper = document.querySelector('.notification-wrapper');
                    notificationWrapper.style.height = 'auto';
                    this.isShowNotification = true;

                    //Only bottom
                    if (bottomshowtime == null) {
                        bottomshowtime = 1450; // 为空，默认显示1.45秒
                    }
                    this.isNotificationBottomShowCloseBtn = bottomshowtime > 2999 ? true : false; //3秒或以上 显示关闭按钮
                    if (position == 'bottom') {
                        // 设置定时器并保存返回值给this.notificationTimeoutId
                        this.notificationTimeoutId = setTimeout(() => {
                            this.isShowNotification = false;
                            this.notificationTimeoutId = -1;
                        }, bottomshowtime);
                    }
                    if (position == 'center') { //动态设置原因：添加滚动层div后，如果notification-wrapper的height设为自动，滚动条会失效，找不到解决方法
                        this.$nextTick(() => {
                            const clientHeight = document.documentElement.clientHeight;
                            const notificationWrapper = document.querySelector('.notification-wrapper');
                            const notifHeight = notificationWrapper.offsetHeight;
                            if (notifHeight > (clientHeight * 0.6)) {
                                notificationWrapper.style.height = '65%'; //内容多，设为65%高度，弹窗尺寸会跟随屏幕变化，滚动条有效
                            }
                            //else:  notificationWrapper.style.height = 'auto'; //内容少，设为自动高度，弹窗尺寸固定。弊端：无法显示滚动条
                            //console.log('documentElement clientHeight:' + clientHeight + '\r\n\r\noffsetHeight:' + notifHeight );
                        });
                    }
                }, 50);
            },
            // 关闭 隐藏消息通知 通知提示框 tooltips
            hideNotification() {
                clearTimeout(this.notificationTimeoutId);
                this.isShowNotification = false;
            },
            //说明 帮助 小问号 mini Help
            showMiniHelp(position) {
                let notif = '';
                let title = '';
                let notifcn = '';
                let notifen = '';
                let text_help = this.settingsLanguage == 'cn' ? '帮助' : 'Help';

                switch (position) {
                    case 'B-CurrentSystemPrompt':
                        notifcn = '展示当前对话的已生效的系统提示词，不可编辑。\r\n\r\n添加、修改、取消请在编辑区操作，保存后生效。\r\n\r\n取消方式：清空编辑区输入框的内容，然后保存，即可取消系统提示词。\r\n\r\n当前系统提示词属于对话数据，与对话一一对应，切换不同的对话，系统提示词会随之变化。';
                        notifen = 'Display the system prompt that is in effect for the current chat and cannot be edited.\r\n\r\nTo add, modify, or cancel the system prompt, please operate in the [Prompt Editor] and save for changes to take effect.\r\n\r\nTo cancel the system prompt, clear the input box in the [Prompt Editor] and save.\r\n\r\nThe system prompt is part of the chat data and is associated with each individual chat. When switching to a different chat, the system prompt will change accordingly.';
                        title = '';
                        break;
                    case 'B-PromptEditor':
                        notifcn = '可以在这里自由编辑提示词。\r\n\r\n未保存的提示词不会影响到[当前系统提示词]。\r\n\r\n提示词编辑器的草稿会存储在[提示词]数据中。它不属于[对话]数据，在对话列表中切换对话时，不会影响提示词编辑器中的内容。';
                        notifen = 'You can freely edit the prompt.\r\n\r\nUnsaved changes will not affect the system prompt for the current chat.\r\n\r\nThe drafts in the Prompt Editor are saved in the Prompt Generator Data and do not belong to the Chat Data. Switching to a different chat in the Chat List will not affect the content in the Prompt Editor.';
                        title = '';
                        break;
                    case 'A-CheckAPIKey':
                        notifcn = 'OpenAI API Key，即OpenAI的接口密钥，将密钥粘贴到输入框，然后开始验证。\r\n\r\n  • 验证成功后，开始聊天或查询API余额 (余额查询于7月22日失效)。\r\n\r\n  • 自动轮换 API Key 功能无法关闭。支持Key数量：1 个或以上。点击 [自动轮换API Key] 可以批量添加。';
                        notifen = 'Enter OpenAI API Key and check.\r\n\r\n  • After checking, you can chat or check OpenAI API balance (The balance inquiry expired on July 22nd).\r\n\r\n  • [Auto Key Rotation] function cannot be turned off. Supports Key quantity: 1 or more. Clicking [Auto Key Rotation] allows for bulk addition of Keys.';
                        if (!this.isSimpleMode) {
                            notifcn += '\r\n\r\n  • API Key是什么样的？\r\n    OpenAI公司的API Key是以“sk-”开头的一串密钥，例如 sk-bkR8V4tjmD7lXq9aZheZVpc6DtR8vw9VTYFJ1iaohMPpT3Bl\r\n\r\n  • 如何获取官方的API Key？\r\n    注册成功后，打开官方接口页面免费获取即可，地址 https://platform.openai.com/account/api-keys   但是，国内IP无法直接注册OpenAI账号，网上有很多注册教程，其中最基础的条件是需要优质的魔法（"魔法"是一种可以让您的手机或电脑出趟国的技术，好的魔法会让OpenAI认为您的设备是真的在国外），满足这个条件就有机会免费注册账号并拥有API Key。';
                            notifen += '\r\n\r\n  • Q: What does API Key look like?\r\n    A: It starts with "sk-", like this: sk-bkR8V4tjmD7lXq9aZheZVpc6DtR8vw9VTYFJ1iaohMPpT3Bl ';
                        }
                        notifcn += '\r\n\r\n附 常用的官方网址：\r\n    OpenAI官网：https://openai.com \r\n    查看API余额：https://platform.openai.com/account/usage \r\n    官方版ChatGPT / GPT-4：https://ai.com 或  https://chat.openai.com/auth/login \r\n';
                        notifen += '\r\n\r\n  • OpenAI: https://openai.com \r\n    OpenAI - API Keys: https://platform.openai.com/account/api-keys \r\n    OpenAI - API balance: https://platform.openai.com/account/usage \r\n    OpenAI - ChatGPT / GPT-4: https://ai.com or https://chat.openai.com/auth/login \r\n';
                        title = '';
                        break;
                    case 'A-APISettings':
                        notifcn = '  • 智能max_tokens(推荐开启): 启用“智能max_tokens”功能后，会出现小问号 [?] ，可以点击了解更多。\r\n\r\n  • temperature: 默认0.7，官方默认1.0。temperature ("温度") 用于控制GPT生成结果的随机性/多样性/创造力。过低 会导致生成的文本更加保守和重复。过高 会导致生成的文本更加随机和具有创造性 (约从1.2开始，就有几率出现无中生有的词或乱码) 。\r\n\r\n  • max_tokens: 生成结果时的最大token数，可随时调节。单轮对话 "上下文已消耗的token数量" 加上设置的 "max_tokens数量" 不能超过模型的上下文长度。\r\n\r\n • model: 模型，暂时仅支持gpt3.5模型。最新带有“16k”后缀的模型 (例: gpt-3.5-turbo-16k 、gpt-3.5-turbo-16k-0613) 支持 16384 个 token，GPT记忆较之前版本翻了四倍。\r\n\r\n • 更多: 点击显示更多的API参数。';
                        //含单引号
                        notifen = "  • Smart max_tokens (recommended to enable): After enabling the 'Smart max_tokens' feature, a small question mark [?] will appear, which can be clicked to learn more.\r\n\r\n  • temperature: The 'temperature' setting controls how creative and diverse the text generated by GPT is. Lower values result in more repetitive and simpler text, while higher values result in more random and creative text. \r\n\r\n  • max_tokens: The maximum number of tokens to generate in the chat completion. The total length of input tokens and generated tokens is limited by the model's context length.\r\n\r\n  • model: Only supports GPT3.5 for now. The latest models with the '16k' suffix (e.g. gpt-3.5-turbo-16k, gpt-3.5-turbo-16k-0613) support 16384 tokens.\r\n\r\n  • More：Click to display more API parameter settings.\r\n\r\n  • OpenAI - API reference: \r\n    https://platform.openai.com/docs/api-reference/chat/create ";
                        title = '';
                        break;
                    case 'A-SmartMaxTokens':
                        notifcn = '开启“智能max_tokens”后，全自动调节 max_tokens，无需手动调节。\r\n如果关闭“智能max_tokens”，需要手动调节 max_tokens，且可能需要多次调节，才能让上下文（记忆）达到高使用率。\r\n\r\n  • 默认开启“智能max_tokens”\r\n  • 这是一个非精准调节的计算策略。长对话中，95%可以做到“智能”，5%遇到错误会自动关闭，并提示您手动调节。';
                        notifen = 'When "Smart max_tokens" is enabled, max_tokens will be automatically adjusted without manual intervention. \r\nIf "Smart max_tokens" is disabled, you will need to manually adjust max_tokens, which may require multiple adjustments to get the context tokens close to the upper limit of the model. \r\n\r\n  • "Smart max_tokens" is enabled by default.\r\n  • This is not an OpenAI algorithm, but it can be "smart" about 95% of the time for longer chats. If errors occur, it will automatically disable and prompt you to manually adjust the max_tokens.';
                        if (!this.isSimpleMode) {
                            notifcn += '\r\n  • 如何做到精准调节？GPT-3.5 (ChatGPT) 与 GPT-4 模型均采用 cl100k_base 编码器，cl100k_base 可以精准计算 tokens 。您可以自己修改代码以达到完美的效果。\r\n  • 以下现象与OpenAI官方有关：发送的第一条消息，额外有增加 13 tokens，之后发送的消息，每条增加 10 tokens。';
                            notifen += '\r\n  • How to achieve precise adjustments?  Both GPT-3.5 (ChatGPT) and GPT-4 models use the cl100k_base encoder, which can accurately calculate tokens. If you are familiar with this technology, you can modify the code to achieve perfect results.\r\n  • The following phenomenon is related to OpenAI: the first message sent will have an additional 13 tokens, and each subsequent message will have an additional 10 tokens added.';
                        }
                        title = '';
                        break;
                    case 'A-ProfessionalAPISettings':
                        notifcn = '  • 接口网址: 目前仅支持GPT-3.5或更新版本的GPT模型，对应的接口网址应以 "/v1/chat/completions" 结尾，如OpenAI官方接口网址 "https://api.openai.com/v1/chat/completions" ，但也不排除第三方接口的网址结构有所改动。\r\n  • 官方API接口需要魔法才能连接。第三方API有的需要魔法，有的不需要。\r\n  • 有魔法的情况下，推荐使用官方接口，实际体验更好 (连发3条不会报错。超过1分钟未使用，连发3条不会限制，超3条后有20秒限制) 。免费gpt账号目前都有每分钟3次的调用限制 (同一账号下多个Key会共享限制，所以轮询Key要用不同账号的Key) ，但官方接口使用起来明显更流畅，报错几率会低点。\r\n  • 第三方接口无法保证隐私安全，请自辨。';
                        if (!this.isSimpleMode) {
                            notifcn += '\r\n  • "魔法"是一种可以让您的手机或电脑出趟国的技术。';
                        }
                        notifcn += '\r\n\r\n  • 以下3个参数的说明见官网：\r\n    top_p: (默认为1)  https://platform.openai.com/docs/api-reference/chat/create \r\n    presence_penalty: (默认为0)  https://platform.openai.com/docs/api-reference/chat/create \r\n    frequency_penalty: (默认为0)  https://platform.openai.com/docs/api-reference/chat/create ';
                        notifen = '  • API URL: API Endpoint. The code only supports GPT-3.5 or newer versions of the model, and the API ENDPOINT URL should end in "/v1/chat/completions", like the official one "https://api.openai.com/v1/chat/completions" \r\n\r\n  • top_p: (Defaults to 1)  https://platform.openai.com/docs/api-reference/chat/create \r\n\r\n  • presence_penalty: (Defaults to 0)  https://platform.openai.com/docs/api-reference/chat/create \r\n\r\n  • frequency_penalty: (Defaults to 0)  https://platform.openai.com/docs/api-reference/chat/create ';
                        if (!this.isSimpleMode) {
                            //新用户模式
                            notifcn += '\r\n\r\n####以下内容均引用自网络：\r\nhttps://zhuanlan.zhihu.com/p/606573556 \r\n\r\n';
                            notifcn += '1) top_p:   0.0 ~ 1.0 \r\n- 指定生成文本的多样性。与参数 temperature (温度) 相似，它是一个可用于代替 temperature 的参数，可以控制生成文本的随机性。\r\n- 会根据生成文本的概率分布，只保留前面概率之和达到一定阈值的词语，然后再从中随机选择一个词语作为下一个生成的词语。较高的值会导致生成的文本更具有多样性，但是会牺牲一定的准确性和保真度。较低的值则会导致生成的文本更加保险和准确，但是可能会过于死板和缺乏新意。\r\n\r\n';
                            notifcn += '2) presence_penalty:   -2.0 ~ 2.0 \r\n- 正值会根据新 tokens 在文本中的现有频率对其进行惩罚，从而降低模型逐字重复同一行的可能性。\r\n- 控制重复单词的惩罚力度。较大的惩罚力度会导致生成文本中不太可能出现相同的单词，而较小的惩罚力度则会容忍一定程度的重复。\r\n- 控制文本罕见词汇出现情况。当此参数值大于0时，将抑制模型生成频繁出现的单词，并鼓励生成罕见的单词。值越大，生成的文本中罕见单词的数量可能越多。如果值为0，则不会考虑单词的频率，并且每次生成的单词概率分布都是相同的。如果需要鼓励生成罕见的单词，可以增大参数值。\r\n\r\n';
                            notifcn += '3) frequency_penalty:   -2.0 ~ 2.0 \r\n- 正值会根据到目前为止是否出现在文本中来惩罚新 tokens，从而增加模型谈论新主题的可能性。\r\n- 控制模型生成与文本样本中不同的单词的惩罚力度。较大的惩罚力度会导致生成文本中更多地包含文本样本中未出现的单词，而较小的惩罚力度则会限制生成文本的多样性。\r\n- 控制文本同一词汇重复情况。当此参数值大于0时，将鼓励模型生成不同的单词，并尽可能避免使用已经在之前生成的文本中出现过的单词。如果值越大，生成的文本中不同单词的数量可能越多。如果值为0，则不会考虑之前生成的单词，并且每次生成的单词概率分布都是相同的。 例如，在文本生成任务中，如果需要鼓励生成不同的单词，并且避免使用相同的单词，可以增大参考值。';
                        }
                        title = '';
                        break;
                    case 'A-GeneralSettings':
                        notifcn = '  • 关于头像设置：支持"上传图片"、"网络图片网址"、"本地图片路径"三种方式。\r\n\r\n  • 1) 如何上传图片？(推荐，支持手机和电脑)\r\n    点击 [设置] > 右侧头像，或者点击左侧下拉框选择上传图片。\r\n\r\n  • 2) 如何使用网络图片？\r\n    填入图片网址，并点击更新。\r\n\r\n  • 3) 电脑端如何使用"本地图片路径"？\r\n    需要把html文件下载到电脑中，在本地打开使用时，才支持本地图片路径（填入相对路径或绝对路径的方式，不明白的建议直接使用第一种方式上传本地图片）。\r\n    以下是本地图片路径的示范：\r\n    Mac/Windows上的相对路径例子：123.jpg （头像需要与html文件放在同一文件夹中）\r\n    Mac上的绝对路径例子：/Users/LinTongXue/Downloads/123.jpg \r\n    windows上的绝对路径例子：D:\\LinTongXue\\123.jpg ';
                        notifen = '  • Regarding avatar settings: Upload image, Network image, and Local image (path).\r\n\r\n  • 1) How to upload an image as an avatar? (Recommended, supports both mobile and computer devices) \r\n    Click on "Settings" > Avatar on the right or select "upload an image" from the dropdown menu on the left.\r\n\r\n  • 2) How to use an online image?\r\n    Enter the URL of the image and click on update.\r\n\r\n  • 3) How to use a local image (path) as your avatar on Mac/PC?\r\n    You need to download the HTML file to your computer and open it locally to support local images (support relative or absolute paths). \r\n    Here are some examples of local image paths:\r\n    Mac/Windows relative path:   123.jpg   (the avatar needs to be placed in the same folder as the HTML file)\r\n    Mac absolute path:   /Users/LinTongXue/Downloads/123.jpg\r\n    Windows absolute path:   D:\\LinTongXue\\123.jpg';
                        title = '';
                        break;
                    case 'A-ExportChatHistory':
                        notifcn = '  • 将当前对话的聊天记录导出为纯文本格式，可直接阅读。\r\n\r\n  • 导出聊天记录 · 单轮记忆：导出单轮聊天记录，即针对当前的上下文记忆进行导出，包含最新修改的系统提示词，不含发送失败的、撤回的、重问的记录。格式：Markdown。文件类型：.md 或 .txt\r\n\r\n  • 导出聊天记录/日志 · 全部：将网页上的所有聊天记录（包含发送失败的、撤回的、重问的记录）和所有操作记录（包含系统提示词修改记录）导出并下载。格式：Markdown。文件类型：.md 或 .txt\r\n\r\n  • 如果导出时没有反应或下载失败，可能是因为浏览器或设备不兼容。\r\n\r\n  • 附 两个Markdown在线编辑器网站：\r\n    https://stackedit.cn/app# \r\n    http://tool.pfan.cn/markdown ';
                        notifen = '  • Export the chat history of the current chat as plain text format for easy reading.\r\n\r\n  • Export Chat History for a Single Round, which includes the current context and the most recent system prompt, but excluding failed messages, undo messages, and resend messages. Exported file format: Markdown. File type: .md | .txt\r\n\r\n  • Export All Chat History / Logs, export and download all chat history (including failed messages, undo messages, resend messages) and all operation records (including system prompt modification records) on the webpage. Exported file format: Markdown. File type: .md | .txt \r\n\r\n  • If there is no response or the download fails when exporting, it may be due to browser or device incompatibility.\r\n\r\n  • Websites for Markdown online editors:\r\n    https://dillinger.io \r\n    https://stackedit.io/app# ';
                        title = '';
                        break;
                    case 'C-ChatList':
                        notifcn = '  • 单个对话的数据包含聊天历史记录 (含上下文记忆)、当前系统提示词 (单条)、聊天输入框内的草稿、以及与对话直接关联的其他数据。';
                        notifcn += '\r\n  • 对话的数据不包括用户设置、用户头像、提示词模板、提示词编辑器草稿、密钥 (API Key) 及API参数信息，切换不同对话时，这些数据不会发生改变。';
                        notifcn += '\r\n  • 可导出单个对话数据，或一次导出所有对话的数据。可用于分享，对方导入后可恢复对话数据，完全一致。';
                        notifcn += '\r\n\r\n  • 若浏览器支持本地存储 (localStorage) ，所有对话数据将会自动保存在浏览器缓存中。';
                        notifcn += '\r\n  • 非隐私模式 (无痕模式) 下，会同步不同标签页间的数据，自动新增、修改、合并，数据冲突会自动创建副本。';
                        notifcn += '\r\n  • 可随时通过 [删除数据] 功能或浏览器的清除历史记录 (清除缓存) 功能来删除所有数据。';
                        if (!this.isSimpleMode) {
                            notifcn += '\r\n  • 对话列表按照最近活动时间排序，新的靠前。';
                            notifcn += '\r\n  • #编号 和 #描述 可随意修改，仅有长度限制。';
                            notifcn += '\r\n  • 手动或自动创建对话副本时，副本的创建时间会自动加1秒或更多。';
                            notifcn += '\r\n  • 新对话 (未发送过消息) 不会被保存。';
                            notifcn += '\r\n  • 对话日志: 单个对话的所有记录 (包含操作记录、撤回的内容以及发送失败的内容等)，用于导出 Markdown 格式的纯文本聊天记录，有些对话日志可能会比较大。';
                            notifcn += '\r\n\r\n  • 使用浏览器普通模式时：标签页或浏览器窗口可随时刷新或关闭，打开时均能恢复数据，且不同标签页的数据是共享的 (同一浏览器打开相同网页的情况下)。';
                            notifcn += '\r\n  • 使用浏览器隐私模式 (无痕模式) 时：大多数支持本地存储，标签页可刷新，标签页或窗口之间不互通数据，标签页关闭时，所有数据都会被销毁。';
                        }
                        notifen = '  • Each chat data includes chat history, the current system prompt, the draft in the chat input box, and other data directly associated with the chat.';
                        notifen += '\r\n  • Chat data does not include user settings, avatars, prompt generator data, prompt editor drafts, API keys, and API parameter information. These data will not change when switching between different chats.';
                        notifen += '\r\n  • You can export individual chat data or export all chats at once. This can be used for sharing, and the recipient can import and restore the chat data.';
                        notifen += '\r\n\r\n  • If the browser supports local storage (localStorage), all data will be automatically saved in the browser cache.';
                        notifen += '\r\n  • In non-private mode, data is synced between different tabs, automatically adding, modifying, and merging. In case of conflicts, copies are created automatically. ';
                        notifen += "\r\n  • You can delete all data anytime through [Settings] > [Delete Data] (or by clearing the browser's browsing history).";
                        if (!this.isSimpleMode) {
                            notifen += '\r\n  • The chat list is sorted by last activity time with the newest at the top.';
                            notifen += '\r\n  • #ID and #description can be freely modified, with length limitations.';
                            notifen += '\r\n  • When manually or automatically creating a copy of a chat, the chat creation time of the copy will be incremented by 1 second or more.';
                            notifen += '\r\n  • New chat (with no messages sent) will not be saved.';
                            notifen += '\r\n  • Chat Logs: All records of individual conversations, including activity logs, retracted messages, and failed messages, are stored. These logs can be exported as plain text chat records in Markdown format. Some logs of chats may be large in size.';
                            notifen += '\r\n\r\n  • In the normal mode of the browser, Tabs or browser windows can be refreshed or closed at any time, and data will be restored upon reopening. Data is shared between different tabs (when opening the same webpage in the same browser).';
                            notifen += '\r\n  • In private/incognito mode, Local storage is supported in most cases, tabs can be refreshed, but data is not shared between tabs or windows, and cache data is cleared when tabs are closed.';
                        }
                        title = '';
                        break;
                    case 'A-DataPrivacyAndManagement':
                        notifcn = '  • 所有数据仅存储于浏览器本地存储 (localStorage) 中，不会通过任何方式对外传输。\r\n';
                        notifcn += '  • [删除数据] 可清空所有数据，或者通过浏览器的历史记录清除功能来清空数据 (清除缓存)。';
                        if (!this.isSimpleMode) {
                            notifcn += '\r\n  • localStorage是浏览器提供的功能，用于在用户的本地设备上存储数据。 1) localStorage与域名相关联，同一个域名下的页面可以共享同一个localStorage。 2) 对于本地运行的HTML文件 (地址以 file:/// 开头)，localStorage通常是共享的。同一浏览器下，不同的本地网页共用着同一个localStorage (以实际情况为准)。 3) 不同浏览器数据不互通。 4) 在浏览器隐私模式下，通常每个标签页都有独立的localStorage。';
                        }

                        notifcn += '\r\n\r\n  • LinGPT数据:  全自动保存\r\n';
                        notifcn += '    数据分为4个部分，支持一键删除或导出，也可以单独删除或导出，同时支持分享和导入，导入时会自动分析包含的数据类型。\r\n\r\n';

                        notifcn += '  • 1) Chat Data List:  对话数据，包含所有对话的数据。可导出单个对话或全部对话。\r\n';
                        if (!this.isSimpleMode) {
                            notifcn += '    更新方式：自动合并数据\r\n    导入数据：自动合并数据\r\n    多标签页：实时同步数据\r\n\r\n';
                        }
                        notifcn += '  • 2) API Key Data:  密钥数据，包含 API Key 数据\r\n';
                        if (!this.isSimpleMode) {
                            notifcn += '    更新方式：自动合并数据\r\n    导入数据：自动合并数据\r\n    多标签页：实时同步数据\r\n\r\n';
                        }
                        notifcn += '  • 3) Prompts Data:  提示词数据，包含提示词窗口的数据 (不包括当前系统提示词)  *多功能待开发中\r\n';
                        if (!this.isSimpleMode) {
                            notifcn += '    更新方式：自动合并数据\r\n    导入数据：自动合并数据 *功能待开发中\r\n    多标签页：手动刷新可获取最新数据 \r\n\r\n';
                        }
                        notifcn += '  • 4) User Settings:  用户设置类数据，包括设置、上传的头像和 API 参数设置\r\n';
                        if (!this.isSimpleMode) {
                            notifcn += '    更新方式：覆盖旧数据\r\n    导入数据：覆盖旧数据\r\n    多标签页：\r\n';
                            notifcn += '     - 每个刷新的页面，在刷新前都会自动保存 [设置数据]，刷新后又会读取 [设置数据] 。因此，设置不会发生变化。\r\n';
                            notifcn += '     - 刷新、关闭、切换标签页等操作都会触发自动保存，所以最新的 [设置] 通常以最后一个操作的网页为准。\r\n\r\n';
                        }
                        notifen = "  • All data is stored only in the browser's local storage (localStorage) and is not transmitted externally.\r\n";
                        notifen += "  • [Delete Data] can clear all data or use the browser's history clearing function to empty the data (clear cache).";
                        if (!this.isSimpleMode) {
                            notifen += "\r\n  • localStorage is a browser-provided feature that allows storing data on the user's local device. 1) localStorage is associated with a specific domain, enabling sharing of the same localStorage among pages within the same domain.  2) For locally-run HTML files (address starting with file:///), localStorage is generally shared. In the same browser, different local web pages share the same localStorage (depending on the actual situation).  3) localStorage is not shared across different browsers.  4) In privacy mode, typically each tab has its own isolated localStorage.";
                        }

                        notifen += '\r\n\r\n  • LinGPT Data: Fully automated saving\r\n';
                        notifen += '    The data is divided into four parts, supporting one-click deletion or export. It also allows individual deletion or export, and supports sharing and importing. When importing, it automatically analyzes the included data types.\r\n\r\n';

                        notifen += '  • 1) Chat Data List: Including data from all chats. Can export single or all chats.\r\n';
                        if (!this.isSimpleMode) {
                            notifen += '    Update method: Automatically merge data\r\n    Import method: Automatically merge data\r\n    Multiple tabs: Real-time data synchronization\r\n\r\n';
                        }
                        notifen += '  • 2) API Key Data: Including API Key data *Functionality under development\r\n';
                        if (!this.isSimpleMode) {
                            notifen += '    Update method: Automatically merge data\r\n    Import method: Automatically merge data\r\n    Multiple tabs: Real-time data synchronization\r\n\r\n';
                        }
                        notifen += '  • 3) Prompts Data: Including data from the prompt generator window (excluding current system prompt) *Functionality under development\r\n';
                        if (!this.isSimpleMode) {
                            notifen += '    Update method: Automatically merge data\r\n    Import method: Automatically merge data *Functionality under development\r\n    Multiple tabs: Manually refresh to get the latest data\r\n\r\n';
                        }
                        notifen += '  • 4) User Settings: Including configurations, uploaded user avatar and API parameter settings\r\n';
                        if (!this.isSimpleMode) {
                            notifen += '    Update method: Overwrite old data\r\n    Import method: Overwrite old data\r\n    Multiple tabs:\r\n';
                            notifen += '     - Each refreshed page will automatically save settings data before refreshing, and then read settings data after refreshing. Therefore, settings will not change.\r\n';
                            notifen += '     - Refreshing, closing, switching tabs, and other operations will trigger automatic saving. Therefore, the latest settings are usually based on the last operation on the webpage.';
                        }

                        //不支持本地存储时
                        if (!this.isLocalStorageSupported) {
                            notifcn = '当前浏览器或浏览器模式不支持"浏览器本地存储"，已关闭保存功能，并不会存储任何数据。但是您仍然可以通过使用 [删除数据] 进行强制清空数据，即使当前浏览器本地存储中不应该存在有数据。\r\n\r\n';
                            notifen = 'The browser or browser mode does not support "browser local storage". The save function has been disabled, and no data will be stored. However, you can still use the "Delete Data" option to forcefully clear any data, even though there should not be any data present in the browser local storage.';
                        }

                        title = '';
                        break;
                    case 'D-AutoKeyRotation':
                        notifcn = '  • 自动轮换 API Key 算法是根据订阅计划和免费计划的密钥特性设计的。结合 OpenAI 错误代码，根据实际使用情况动态标记 API Key 并智能调用，无需人工干预。';
                        notifcn += '\r\n  • [自动轮换API Key] 与 [自动重问] 的结合：当请求出现错误时，会自动判断是否需要  [自动重问] 。为了提供更好的用户体验，自动重试不会无限制进行，有些情况会中止重试，而需要自动重试的情况会综合考虑错误类型、已重试次数和已等待时间来判断是否适合通过轮换 Key 来重发请求。';
                        notifcn += '\r\n  • 自动轮换 API Key 功能无法关闭。支持 Key 数量：1 个或以上。';
                        notifcn += '\r\n  • 对于非付费用户，如果您有两个 OpenAI 免费计划的账号，将获得很好的体验。建议往 LinGPT 添加每个 OpenAI 账号的 Key 的数量为 2～3 个 (虽然相同账号下的Key是共享请求配额的)，当然，每个账号只取  1 个 Key 也是可以的。';
                        notifcn += '\r\n  • 单个添加和批量添加没有区别。单个添加时不会记录无效 Key，而批量添加时，无效 Key 会在被记录。';
                        notifcn += '\r\n  • 批量添加支持识别两种格式：1）每行一个以"sk-"开头的 key；2）识别文本中带双引号的以"sk-"开头、长度为 51 位的 Key（支持不换行的文本，比如直接复制 JSON 文件的文本内容）。';
                        notifcn += '\r\n  • 批量添加时，不会立即验证有效性。如果添加了大量无效 Key，请求过程中，当单次连续检测到 20 个无效 Key 时，检测会中止。';
                        notifcn += '\r\n  • 排序会自动变化，显示的是上次轮询时的排序。';
                        notifcn += '\r\n  • 可修改级别，点击 API Key 的级别，根据提示进行修改。通常不需要修改级别。如果您的付费 Key 没有被标记为付费，这不是一个问题，不会影响实际使用。当出现连续被调用的情况，并达到鉴别标准时，系统会自动标记为付费。这需要一个过程，取决于添加 Key 的数量、请求的频率以及 Key 是否独享的情况。';
                        notifcn += '\r\n  • 如果想恢复之前被标记为"超出配额"的 Key，可以手动修改它的级别，优先改成 0 或 1。';
                        notifcn += '\r\n  • 批量删除：[设置] > [删除数据]，删除所有 API Key 。';
                        notifcn += '\r\n  • 如果需要修改 LinGPT 的 JSON 数据文件，推荐 JSON 在线编辑工具：https://jsoneditoronline.org';

                        notifen = '  • The algorithm of [Auto Key Rotation] is designed according to the characteristics of keys in subscription and free plans. Combining with OpenAI error codes, API Key is dynamically marked and intelligently called based on actual usage.';
                        notifen += '\r\n  • [Auto Key Rotation] and [Auto Retry]: When a request encounters an error, the system will automatically determine whether to retry. To provide a better user experience, auto-retry will not be performed indefinitely. Some situations may stop retrying, while situations that require auto-retry will be judged comprehensively based on error types, number of retries, and waiting time to determine whether it is suitable to resend the request by rotating the Key.';
                        notifen += '\r\n  • [Auto Key Rotation] function cannot be turned off. Supports Key quantity: 1 or more.';
                        notifen += '\r\n  • For non-paying users, if you have two OpenAI free plan accounts, you can get a better user experience. It is recommended to add 2-3 Keys for each OpenAI account added to LinGPT (although Keys under the same account share request quota), and of course, only adding 1 Key for each account is also acceptable.';
                        notifen += '\r\n  • There is no difference between adding a single Key or adding Keys in bulk. Invalid Keys will not be recorded when adding a single Key, while invalid Keys will be recorded when adding Keys in bulk.';
                        notifen += '\r\n  • Adding Keys in bulk supports identifying two formats: 1) one key starting with "sk-" per line; 2) a Key starting with "sk-" and with a length of 51 characters recognized in a text enclosed in double quotes (Supports text without line breaks, such as directly copying the text content of a JSON file.).';
                        notifen += '\r\n  • When adding Keys in bulk, the system will not immediately check their validity. If a large number of invalid Keys are added, the check will be stopped when 20 consecutive invalid Keys are detected during the request process.';
                        notifen += '\r\n  • The sorting will automatically change, and what you see is the sorting when the Key was last rotated during polling.';
                        notifen += '\r\n  • The level can be modified by clicking on the level of the API Key and following the prompts. Usually, there is no need to modify the level. If your paid Key is not marked as a paid account, it is not an issue and will not impact actual usage. When a Key is called continuously and reaches the identification standard, it will be automatically marked as a paid account. This is a process that depends on the number of Keys added, request frequency, and whether the Key is exclusively used.';
                        notifen += '\r\n  • If you want to restore a Key that was marked as "Quota exceeded", you can manually modify its level, preferably to 0 or 1.';
                        notifen += '\r\n  • Batch deletion: [Settings] > [Delete Data] > Delete all API Keys.';
                        notifen += '\r\n  • If you want to modify the JSON file for LinGPT, we recommend using an online JSON editor tool: https://jsoneditoronline.org';

                        title = '';
                        break;
                    default:
                        // 当expression的值都不等于以上值时执行的代码块
                        notifcn = '';
                        notifen = '';
                        title = '';
                }

                notif = this.settingsLanguage == 'cn' ? notifcn : notifen;
                this.showNotification(notif, 'center', title);
            },

            // 设置窗口 - 打开设置窗口 弹窗
            openDialogTodo_Settings() {

                //打开弹窗前需要处理的：
                //1. API URL： 将当前api url恢复到input，无论是否有效，即打开弹窗时，默认显示当前使用的URL。
                this.inputApiURL = this.apiURL;

                //2. API URL： 保存按钮不可用。url在修改前保持不可用状态
                this.btnDisabledState_SaveApiURL = true;

                //3. API URL： 如果当前是官方url，下拉框匹配官方
                if (this.inputApiURL == 'https://api.openai.com/v1/chat/completions') {
                    this.selectedApiURL = 'https://api.openai.com/v1/chat/completions'; // 下拉框显示为“官方”/“OpenAI”
                } else if (this.inputApiURL == 'https://api.openai-proxy.com/v1/chat/completions') {
                    this.selectedApiURL = 'https://api.openai-proxy.com/v1/chat/completions'; // 下拉框显示为“非官方”/“3rd party”
                }

                //4. img URL： 将当前头像地址img url恢复到输入框input，即打开弹窗时，默认显示当前使用的头像的URL。
                const svgtips = this.settingsLanguage == 'cn' ? this.GPTGreenSVG_UrlText_cn : this.GPTGreenSVG_UrlText_en;
                this.inputGPTImageUrl = this.isgptGreenSvgShow ? svgtips : this.gptAvatarURL;
                this.inputUserImageUrl = this.userAvatarURL;

                //5. img URL： 更新头像按钮不可用。url在修改前保持不可用状态
                this.btnDisabledState_updateUserImageUrl = true;
                this.btnDisabledState_updateGPTImageUrl = true;

                //显示设置弹窗
                this.isShowDialog_Settings = true;
            },
            // 设置窗口 - 简约模式/默认模式 Normal Mode .  新用户模式/教程模式 User Onboarding
            changeSimpleMode(ischange) {

                if (ischange !== false) {
                    let notif = '';
                    if (this.isSimpleMode) {
                        notif = this.settingsLanguage == 'cn' ? '默认模式 (简约模式)' : 'Normal Mode (Simple Mode)';
                    } else {
                        notif = this.settingsLanguage == 'cn' ? '新用户模式 (此模式下会增加提示)' : 'User Onboarding (This mode offers more functionality prompts.)';
                    }
                    this.showNotification(notif, 'bottom', '', 4000);
                }

                //一些CSS样式/布局
                //判断设备宽度 是否显示tokens 的文字 / 图标...
                if (document.documentElement.clientWidth < 450) {
                    //let isshow = Math.random() < 0.6  ; //概率 随机
                    //if(this.isSimpleMode){ isshow = false; }
                    //this.isShowTotaltokensSVG = !isshow;
                    //this.isShowTotaltokensLabel = isshow;//小屏幕可考虑显示
                    this.isShowTotaltokensSVG = true;
                    this.isShowTotaltokensLabel = false;//小屏幕直接不显示
                } else {
                    this.isShowTotaltokensSVG = true;
                    this.isShowTotaltokensLabel = !this.isSimpleMode; // !this.isSimpleMode = ( true && this.isSimpleMode )
                }
                // 判断设备宽度 是否显示上下文/记忆 的文字
                if (document.documentElement.clientWidth < 471) {
                    this.isShowQACountLabel = false;
                } else {
                    this.isShowQACountLabel = !this.isSimpleMode; // !this.isSimpleMode = ( true && this.isSimpleMode )
                }

            },
            // 设置窗口 - 显示专业级API设置 Pro 设置控制面板
            showProConfig() {
                let notif = '';
                if (this.isShowProConfig) {
                    this.isShowProConfig = false;
                    notif = this.settingsLanguage == 'cn' ? '隐藏 API专业设置' : 'Hide Professional API Settings';
                } else {
                    this.isShowProConfig = true;
                    notif = this.settingsLanguage == 'cn' ? '显示 API专业设置' : 'Show Professional API Settings';
                }
                this.showNotification(notif, 'bottom');
            },
            // 设置窗口 - 开启/关闭智能max_tokens  Smart max_tokens
            changeSmartMaxTokens() {
                this.isReminded_SmartMaxTokens = 5;//5次后提醒
                const notif_cn = this.openSmartMaxTokens ? '已启用 智能max_tokens' : '已关闭 智能max_tokens';
                const notif_en = this.openSmartMaxTokens ? 'Smart max_tokens Enabled' : 'Smart max_tokens Disabled';
                const notif = this.settingsLanguage == 'cn' ? notif_cn : notif_en;
                this.showNotification(notif, 'bottom');
            },
            // 判断是否需要禁用网页缩放 user-scalable  *实际上所有情况下都可以缩放，只禁ios，但ios会忽略禁用指令
            isDisableZoomOnIOS() {
                //仅iphone/ipad需要禁用缩放（排除夸克浏览器）。不禁用时，ios设备点击输入框会放大页面，输入后需要手动缩小页面，对于聊天网页而已，体验不好，很麻烦。 禁用后，ios设备中的safari、firefox、chrome、edge都还可以放大，会忽略指令，但是Quark浏览器除外。
                let isIOS = /iPad|iPhone|ios|iPod/.test(navigator.userAgent); // 判断是否为 iOS 设备
                let isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); // 判断是否为 Safari 内核
                let isQuark = /Quark|quark/.test(navigator.userAgent);  // 判断是否为 夸克 浏览器，iphone中Quark会严格执行禁用缩放的命令，而其他浏览器都会忽略
                //条件宽松点，isSafari可能是mac，禁用不影响
                if ((isIOS || isSafari) && !isQuark) {
                    //user-scalable=no
                    return true;
                } else {
                    //user-scalable=yes
                    return false;
                }
            },
            // 设置窗口 - 更改移动端页面显示比例，默认0.8  change Page Scaling  mode: true=selected change  false=manual setting
            changePageScaling(mode) {
                //验证，避免意外错误
                let checkPageScaling;
                if (mode === false) {
                    checkPageScaling = parseFloat(this.pageScaling);// this.pageScaling like '0.8'
                } else {
                    checkPageScaling = parseFloat(this.selectedPageScaling) / 100; // this.selectedPageScaling like '80%'
                    this.selectedPageScaling = '';//复原
                }

                // 判断是否为空
                if (isNaN(checkPageScaling)) {
                    checkPageScaling = 0.8;
                }
                // 判断是否在范围内
                if (checkPageScaling < 0.5 || checkPageScaling > 1) { //通过setAttribute设置，超过100%没有效果
                    checkPageScaling = 0.8;
                }

                // 确保格式正确 保留2位小数的字符串型
                this.pageScaling = checkPageScaling.toFixed(2); // toString

                const user_scalable = this.isDisableZoomOnIOS() ? 'user-scalable=no,' : 'user-scalable=yes,';
                const newContent = 'width=device-width,initial-scale=' + this.pageScaling + ',minimum-scale=0.5,maximum-scale=2,' + user_scalable + 'viewport-fit=cover';
                document.querySelector('meta[name="viewport"]').setAttribute('content', newContent);

                if (mode !== false) {
                    const percent = (parseFloat(this.pageScaling) * 100).toFixed(0) + '%'; //转百分比
                    const notif = this.settingsLanguage == 'cn' ? '网页显示比例已改为 ' + percent + ' (如果网页放大了，请双指操作缩小)' : 'Page display scale has been changed to ' + percent + ' (If the page is zoomed in, use two fingers to zoom out).';
                    const notiftime = this.settingsLanguage == 'cn' ? 6500 : 7500;
                    this.showNotification(notif, 'bottom', '', notiftime);
                    this.changeSimpleMode(false);
                }
            },
            //totaltokens 是否超出 模型max_tokens的最大值
            isTotaltokens_Exceeds_Maxtokens(modeldata) {
                const model_data = modeldata != null ? modeldata : this.gptModelList.find(model => model.modelname === this.apiGPTModel);

                if (this.totaltokens > model_data.maxtokens) { //当前totaltokens 已经超过模型的最大max_tokens数  *注意：这里不要用(this.totaltokens >= model_data.maxtokens )或(this.totaltokens >= model_data.maxtokens - 1 )，否则刚好到用16384的时候，重问会弹窗要求换模型。
                    const notif = this.settingsLanguage == 'cn' ? '当前上下文记忆已使用的 tokens 数量为 ' + this.totaltokens + ' ，已超出当前模型支持的最大 max_tokens 数量 ( ' + model_data.maxtokens + ' )！\r\n请处理历史记录或切换到支持更大 max_tokens 的模型。\r\n\r\n当前模型(model):  ' + this.apiGPTModel : 'The current context has used ' + this.totaltokens + ' tokens, which exceeds the maximum max_tokens ' + model_data.maxtokens + ' supported by the current model! \r\nPlease handle the chat history or switch to a model that supports a larger max_tokens.\r\n\r\nThe current model: ' + this.apiGPTModel;
                    setTimeout(() => {
                        alert(notif);
                    }, 80);//延迟目的。如果是切换模型(下拉框)，弹太快界面上显示的是切换之前的模型。延迟一点，切换后的模型显示出来后，再弹。
                    return true;
                }
                return false;
            },

            // 设置窗口 GPT Model
            changeGPTModel() {
                if (this.checkBusyStatus()) {
                    // 如果返回 true，正在发送或正在验证
                    return;
                }

                let notif = '';
                const model_data = this.gptModelList.find(model => model.modelname === this.apiGPTModel);

                let smart_maxtokens_notif = false; //是否建议开启smart max_tokens
                //切换模型后，修正可能存在的apiMaxTokens不合理的情况 （尤其是在没有开启smart max_tokens的时候）
                if (this.isTotaltokens_Exceeds_Maxtokens(model_data)) { //当前totaltokens 已经超过模型的最大max_tokens数, 弹窗警告 + 返回
                    this.apiMaxTokens = model_data.maxtokens - 1;
                    return;
                }
                if (model_data.maxtokens >= this.totaltokens + 1) { //若没有开启smart max_tokens，避免totaltokens + apiMaxTokens 超过 maxtokens。 保留1，而不是更大的差值，是为了容易被用户注意到apiMaxTokens的设置不合理，引导用户手动修改max_tokens。
                    const diff1 = model_data.maxtokens - this.totaltokens;
                    if (diff1 - this.apiMaxTokens > 200) {
                        //nothing to do  差值200以上，目的：smart_maxtokens_notif = flase，不提醒
                    } else {
                        this.apiMaxTokens = this.apiMaxTokens > diff1 ? diff1 : this.apiMaxTokens;
                        smart_maxtokens_notif = true;
                    }
                }

                if (smart_maxtokens_notif && !this.openSmartMaxTokens) { //建议开启 && 当前未开启
                    notif = this.settingsLanguage == 'cn' ? '不同模型的 max_tokens 上限不同，建议启用 [智能max_tokens] 功能' : 'The max_tokens limit varies for different models. Enable the [Smart max_tokens] feature for optimal results.';
                    this.showNotification(notif, 'bottom', '', 5000);
                } else {
                    notif = this.settingsLanguage == 'cn' ? '"GPT模型 (model)" 变更为 ' + this.apiGPTModel : '"model" changed to ' + this.apiGPTModel;
                    this.showNotification(notif, 'bottom', '', 2000);
                }

                //切换模型后，检测模型是否过期，过期时提醒用户
                if (this.gptModel_CheckExpiration(this.apiGPTModel, 4000)) { //true:该模型过期  延时4秒
                    return;
                }
            },
            // 检测Model是否过期  返回true为过期
            gptModel_CheckExpiration(model_name, delaytime) {
                if (model_name == null || model_name === '') {
                    return false;
                }
                delaytime = delaytime == null ? 0 : delaytime;

                try {
                    for (const model of this.gptModelList) {
                        if (model.modelname === model_name && model.expiredtime !== '') {
                            const currentTime = new Date();// 获取当前时间
                            const expirationDate = new Date(model.expiredtime); // 创建过期时间
                            if (currentTime >= expirationDate) {
                                const date_YYYYMMDD = this.formatDateYYYYMMDDHHmmss(expirationDate, ':').slice(0, 10);
                                const notif = this.settingsLanguage == 'cn' ? model.modelname + ' 于 ' + date_YYYYMMDD + ' 被 OpenAI 弃用，该模型将不再可用。请切换到另一个模型。' : model.modelname + ' has been deprecated by OpenAI on ' + date_YYYYMMDD + ', and this model will no longer be available. Please switch to another model.';
                                setTimeout(() => {
                                    this.showNotification(notif, 'center', '');
                                }, delaytime);
                                return true;
                            } else {
                                return false;
                            }
                        }
                    }
                    return false;
                } catch (e) {
                    console.error('gptModel_CheckExpiration() - 检测Model是否过期', e);
                    return false;
                }
            },
            // 设置窗口 Temperature
            changeTemperature() {
                const notif = this.settingsLanguage == 'cn' ? '"temperature" 变更为 ' + this.apitemperature.toString() : '"temperature" changed to ' + this.apitemperature.toString();
                this.showNotification(notif, 'bottom', '', 1800);
            },
            // 设置窗口 MaxTokens
            changeMaxTokens() {
                if (this.openSmartMaxTokens) {
                    return;//自动调整不会激发事件，可以不用return
                }
                const notif = this.settingsLanguage == 'cn' ? '"max_tokens" 变更为 ' + this.apiMaxTokens.toString() : '"max_tokens" changed to ' + this.apiMaxTokens.toString();
                this.showNotification(notif, 'bottom', '', 1800);
            },
            // 设置窗口 Top
            changeTopP() {
                const notif = this.settingsLanguage == 'cn' ? '"top_p" 变更为 ' + this.apiTopP.toString() : '"top_p" changed to ' + this.apiTopP.toString();
                this.showNotification(notif, 'bottom', '', 1800);
            },
            // 设置窗口 PresencePenalty
            changePresencePenalty() {
                const notif = this.settingsLanguage == 'cn' ? '"presence_penalty" 变更为 ' + this.apiPresencePenalty.toString() : '"presence_penalty" changed to ' + this.apiPresencePenalty.toString();
                this.showNotification(notif, 'bottom', '', 1800);
            },
            // 设置窗口 FrequencyPenalty
            changeFrequencyPenalty() {
                const notif = this.settingsLanguage == 'cn' ? '"frequency_penalty" 变更为 ' + this.apiFrequencyPenalty.toString() : '"frequency_penalty" changed to ' + this.apiFrequencyPenalty.toString();
                this.showNotification(notif, 'bottom', '', 1800);
            },

            //粗略的估算tokens。仅适合 GPT-3.5 & GPT4， 不适合GPT-3 ，tokens计算方式完全不同。有技术的可以直接实现100%精准计算，不需要估算。参考官网，或者：https://gpt-tokenizer.dev
            lingptTokenizer(message) {
                // tool ： https://gpt-tokenizer.dev
                // To support new languages .,
                // 1). you should refer to the englishMixedArr and englishSingleArr to add new codes.  *Different languages are handled differently. The estimation for English is based on words or consecutive letters, while for Chinese, it is based on single characters.
                // 2). Test the new language on the website https://gpt-tokenizer.dev. If the spaces before the words are not counted as one token (1 token), you need to subtract the number of spaces in the "const _space = ..." code, just like how it is handled for English.
                // 3). To estimate the tokens for word groups (or characters) in a language, add the ratio calculation like the way it is handled for English: "const english_tokens = englishMixedArr.length / 0.75 ;".

                // 定义左右是空格的单独连续字符的正则表达式
                const englishSingleReg = /\s+[a-zA-Z]+/g; //左边是空格的连续英文，用于“空格计算”

                // 定义中文、数字、特殊符号的正则表达式
                const chineseReg = /[\u2E80-\u9FFF]/g;
                const englishReg = /[a-zA-Z]+/g;
                const numberReg = /\d+/g;
                const symbolReg = /[^\u2E80-\u9FFFa-zA-Z0-9\s]/g; //符号及其他语言文字

                // 匹配空格、制表符和换行符的正则表达式
                const spaceReg = /\s+/g;
                const otherSpaceReg = /\s{2,}/g; //两个或以上连续

                // 定义结果数组
                const chineseMixedArr = [];
                const numberMixedArr = [];
                const symbolMixedArr = [];
                let englishSingleArr = [];
                let englishMixedArr = [];
                const spaceArr = [];
                const otherSpaceArr = [];
                //const otherArr = [];

                // 提取中文
                const chineseMatch = message.match(chineseReg);
                if (chineseMatch) {
                    chineseMixedArr.push(...chineseMatch);
                }

                // 提取数字
                const numberMatch = message.match(numberReg);
                if (numberMatch) {
                    numberMatch.forEach(num => {
                        if (num.length > 3) {
                            // 超过3个字符的数字需要拆分
                            for (let i = 0; i < num.length; i += 3) {
                                numberMixedArr.push(num.slice(i, i + 3));
                            }
                        } else {
                            numberMixedArr.push(num);
                        }
                    });
                }

                // 提取特殊符号 *符号及其他语言文字
                const symbolMatch = message.match(symbolReg);
                if (symbolMatch) {
                    symbolMixedArr.push(...symbolMatch);
                }

                // 提取英文
                const englishMatch = message.match(englishReg);
                if (englishMatch) {
                    englishMixedArr.push(...englishMatch);
                }

                // 提取英文（前面是空格的英文，用于计算空格所占的tokens）
                const englishMatch_Single = message.match(englishSingleReg);
                if (englishMatch_Single) {
                    englishSingleArr.push(...englishMatch_Single);
                }

                // 提取空格、制表符和换行符
                const spaceMatch = message.match(spaceReg);
                if (spaceMatch) {
                    spaceArr.push(...spaceMatch);//连续1个或以上
                }
                // const spaceMatch = message.match(spaceReg);
                const otherSpaceMatch = message.match(otherSpaceReg);
                // if (spaceMatch) {
                // 	spaceArr.push(...spaceMatch.filter((match) => !otherSpaceReg.test(match)));
                // }
                if (otherSpaceMatch) {
                    otherSpaceArr.push(...otherSpaceMatch);//连续2个或以上
                }

                // 提取剩余未匹配到的字符 *应该是不存在 =null
                //const otherMatch = message.replace(chineseReg, '').replace(englishReg, '').replace(numberReg, '').replace(symbolReg, '').replace(spaceReg, '');
                //if (otherMatch) {
                //	otherArr.push(...otherMatch);
                //}

                // 预估"英文等语言"的tokens  *English, etc. languages.  *测试准确度时，对比的条件要相同：不能有标点符号 不能换行 单词只能用1个空格隔开
                const english_tokens = englishMixedArr.length / 0.75;
                // 预估"中文等语言"的tokens  *Chinese, etc. languages.  *比例可以偏小，这样估算的tokens将偏大，计算的max_tokens将偏小，这样不容易因“max_tokens偏大”而报错。
                const chinese_tokens = chineseMixedArr.length / 0.83;
                // 预估“数字”的tokens
                const numbers_tokens = numberMixedArr.length / 1;
                // 预估“符号及其他语言文字”的tokens  *Symbols and other languages
                const symbolMixed_tokens = symbolMixedArr.length / 1.3; //这里按照1个符号对应0.77个token计算。这部分差异可能较大。有单个符号就占用2～3个token的情况（symbolMixedArr.length / 0.33 ～ 0.5），如果数量多，会非常不准。
                // 预估“空格、制表符和换行符”的tokens
                const _space = spaceArr.length - englishSingleArr.length < 0 ? 0 : spaceArr.length - englishSingleArr.length; //首先排除英文前的空格，其他语言太多，无法判断。比如：俄语 法语 等，也可以同英文，计算排出。
                const _otherspace = otherSpaceArr.length / 2.5; //连续2个以上的空格，增加0.4token。  *连续2个以上的空格，连着非英文字母时，通常都会增加1个token。联系2个换行一般不增加。
                const space_tokens = _space / 1.3 + _otherspace;

                let msgtokens = english_tokens + chinese_tokens + numbers_tokens + symbolMixed_tokens + space_tokens;

                msgtokens = msgtokens * 1.05; //增加5%  Increase by 5%.
                return Math.ceil(msgtokens); //int. 返回预估的tokens ，向上取整数。
            },
            // 智能max_tokens / Smart max_tokens / 自动调节max_tokens。  预估最后一条信息的tokens，智能调节参数。是我自己想的一个预估策略，不准的。如果想要精准，请看下面的方法： （应该是要后台什么的，我不会，不学了）
            // Not precise. If you want to calculate tokens accurately, please refer to: 1)（GPT-3.5）：https://gpt-tokenizer.dev   2) OpenAI (GPT-3, not GPT-3.5）https://platform.openai.com/tokenizer
            // 算法中的数字除了特别说明的，其他都是我自己设定的值。 There are a lot of numbers in the algorithm, which do not have any special meaning, they are all values that I set up myself for the purpose of strategy.
            autoAdjustMaxTokens(sendMsg) {

                if (!this.openSmartMaxTokens) {
                    return;
                }
                if (this.isFixingMaxTokens_SmartMaxTokens) { //“正在修复MaxTokens”的状态，本次不自动调节max_tokens.
                    return;
                }

                //获取当前模型 模型数据 保护模型的参数/属性  Get the current model's model data  - model's parameters/attributes.
                const model_data = this.gptModelList.find(model => model.modelname === this.apiGPTModel);

                //重问时(成功过的/得到过回复后的重问) When retrying. (After receiving a reply from GPT)  *(4095 4096 4097 16384 16385  gpt-3.5 gpt-4 gpt3.5 gpt4)
                if (this.userMsgTokensForRetry != 0) {
                    this.apiMaxTokens = model_data.maxtokens - this.totaltokens; // 此时this.totaltokens为精准的total tokens（如果提示词没有变动/变化不大的话）。 this.totaltokens = 上下文 + 本次重问的信息
                    //这里其实没必要减去一个值，tokens足够的情况下，减低点 以预防“因修改提示词而导致超额”的情况
                    if (this.apiMaxTokens > 4097) { // 4096
                        this.apiMaxTokens = this.apiMaxTokens - 600;
                    } else if (this.apiMaxTokens > 3500) {
                        this.apiMaxTokens = this.apiMaxTokens - 350;
                    } else if (this.apiMaxTokens > 2600) {
                        this.apiMaxTokens = this.apiMaxTokens - 300;
                    } else if (this.apiMaxTokens > 1500) {
                        this.apiMaxTokens = this.apiMaxTokens - 200;
                    } else if (this.apiMaxTokens > 600) {
                        this.apiMaxTokens = this.apiMaxTokens - 100;
                    }
                    // 其余情况：不处理，发生错误则根据提示信息手动调整。
                    return;
                }

                //以下：非重问时 & 失败后的重问。 The following code: Non-Retry operation && Retry operation performed after request failure.
                //:?: 单条发送问题的token数会比实际的高，疑似官方会添加隐藏的提示词。经过测试，1、当this.msgContent包含系统提示词这一项时（[0]，即使为空），首条会额外占用 13 token ； 2、之后每条发送的讯息额外占用 10 token 。
                //:?: The tokens for sending message are higher than the actual count, which is probably because OpenAI adds hidden prompt words. After testing, 1) when the "system prompt"  is included in the context of this.msgContent in the code (this.msgContent[0]), even if the system prompt is empty, the first message will have an extra 13 tokens; 2) each subsequent message will take up an additional 10 tokens.
                //截止2023-06-10，OpenAI规律没有变化。As of June 10th, 2023, there have been no changes in the policies of OpenAI.
                const firstsendmsg_openai = 13;  // tips：this.msgTokens[1]记录的是（提示词+首条）的tokens，且后续修改提示词并不会更新这里的tokens数量
                const sendmsg_openai = 10;


                // 预估当前消息的tokens数量 Estimate the tokens of the current message.
                let estimatedTokens = 0;
                try {
                    estimatedTokens = this.lingptTokenizer(sendMsg);
                } catch (e) {
                    estimatedTokens = Math.ceil(sendMsg.length * 0.75);
                    const notif_cn = '\r\n\r\nSafari?(不排除是浏览器兼容性问题) 出错了(this.lingptTokenizer - 估算tokens时出错了)，可能是因为您发的内容太太太大了～\r\n请避免发送大量文本，建议您撤回刚刚发送的内容。大量文本会占用本地存储空间，而浏览器本地存储通常只支持5-10MB的容量。发送大量文本可能会导致自动保存不断失败而频繁报错。';
                    const notif_en = '\r\n\r\nSafari?(It is possible that this is a browser compatibility issue.) Oops, an error occurred (this.lingptTokenizer - There seems to be an error in the token calculation), possibly because the content you provided is tooooo large!\r\nPlease avoid sending large amounts of text. It is recommended that you withdraw the content you just sent. Large amounts of text can occupy local storage space, and browser local storage usually supports a capacity of only 5-10MB. Sending large amounts of text may result in frequent errors due to continuous failure of automatic saving.';
                    const notif = this.settingsLanguage == 'cn' ? notif_cn : notif_en;
                    this.showNotification('Error Message: ' + e.message + notif, 'center', 'Error - this.lingptTokenizer');
                }
                //	console.log("当前预估的tokens: " + estimatedTokens);
                if (this.msgContent.length < 2) { //如果是首条信息
                    estimatedTokens = estimatedTokens + firstsendmsg_openai;
                    let sysprompt = this.msgContent[0]["content"];
                    if (sysprompt != '') { //如果存在系统提示词
                        const syspromptTokens = this.lingptTokenizer(sysprompt); //预估系统提示词的tokens
                        //	console.log("此为首条消息，加上系统提示词后的预估的tokens（未加上13tokens） : " + syspromptTokens);
                        estimatedTokens = estimatedTokens + syspromptTokens;
                    }
                } else { //非首条，不需要计算系统提示词，因为它的精准tokens数量会被包含在this.totaltokens当中
                    estimatedTokens = estimatedTokens + sendmsg_openai;
                }

                //	console.log("当前预估的tokens（含+13/+10；若是首条消息，这里还包含了提示词的tokens）: " + estimatedTokens);


                // (4095 4096 4097 16384 16385  gpt-3.5 gpt-4 gpt3.5 gpt4) 如果升级为GPT4 GPT-4模型，这里的4096需要修改  If upgraded to the GPT-4 model, the value of 4096 here needs to be modified.
                // 计算需要设置的max_tokens
                // *lingptTokenizer中，预估estimatedTokens的结果会偏大点（多数时候）
                let remainingTokens = model_data.maxtokens - this.totaltokens - estimatedTokens; //预估剩余的 可用于max_tokens的数量
                //	console.log("max_tokens初步设定为: " + Math.ceil(remainingTokens));
                let n = 0;
                let larger_maxtokens_n = 0;
                let larger_maxtokens_i = 0;
                if (model_data.maxtokens < 4100 && estimatedTokens < 500) { //4096
                    n = 200 - (estimatedTokens * 150 / 500); //预估的token越少 n就越大 允许设置max_tokens更高一点； *没有什么特别，不这么做也可以
                } else if (model_data.maxtokens > 16300 && estimatedTokens < 500) {
                    larger_maxtokens_n = 200 - (estimatedTokens * 150 / 500);
                }

                if (model_data.maxtokens > 4100) { //16384
                    larger_maxtokens_i = remainingTokens / 4096 * 400;
                }

                //v7.29 回顾/备忘：1 主要基于“多数情况下lingptTokenizer的预估值会偏大”，默认发送内容的token越大，预留的误差量也就越大。 2 以下代码仅是微调  3 以下数值均没有特殊意义
                //  4 当remainingTokens接近模型的max_tokens上限时，说明提问内容的token很小，所以使用n进一步微调，调的不多，意义也不大。另外，此时即使提问只用了10来个token，也会算成200来个token。因为gpt通常不会回复达到“接近4000token（上限）”或者“接近165000token（上限）”的内容量，所以这种情况下，多计算些误差并不会影响使用体验与回复质量。
                // remainingTokens = Math.ceil(remainingTokens); //向上取整数
                if (remainingTokens > 15800 && model_data.maxtokens > 16300 && model_data.maxtokens < 16400) { //模糊的指定16384，如果有超过16384的新模型，应该跳至remainingTokens = remainingTokens - larger_maxtokens_i
                    remainingTokens = remainingTokens - 400 + larger_maxtokens_n;
                } else if (remainingTokens > 15000 && model_data.maxtokens > 16300 && model_data.maxtokens < 16400) { //模糊的指定16384
                    remainingTokens = remainingTokens - 370 + larger_maxtokens_n;
                } else if (remainingTokens > 4096) { // 超过 4096 的模型
                    remainingTokens = remainingTokens - larger_maxtokens_i;
                } else if (remainingTokens > 3500) {
                    remainingTokens = remainingTokens - 400 + n;
                } else if (remainingTokens > 2500) {
                    remainingTokens = remainingTokens - 370 + n;
                } else if (remainingTokens > 1500) {
                    remainingTokens = remainingTokens - 300;
                } else if (remainingTokens > 1000) {
                    remainingTokens = remainingTokens - 250;
                } else if (remainingTokens > 600) {
                    remainingTokens = remainingTokens - 200;
                } else if (remainingTokens > 500) {
                    remainingTokens = remainingTokens - 100;
                } else if (remainingTokens > 300) {
                    remainingTokens = remainingTokens - 80;
                } else if (remainingTokens > 100) {
                    remainingTokens = remainingTokens - 35;
                } else if (remainingTokens > 25) {
                    remainingTokens = remainingTokens - 10;
                } else if (remainingTokens > 0) {
                    remainingTokens = (remainingTokens - 3) > 0 ? (remainingTokens - 3) : remainingTokens;
                } else { // <= 0
                    //"Smart max_tokens"驱动的自动重问功能，计算错误 > 因为max_tokens设置过高而导致报错 > 从报错信息中获得精准的tokens（msg） > 精准设置max_tokens > 自动重问
                    // (4095 4096 4097 16384 16385  gpt-3.5 gpt-4 gpt3.5 gpt4)
                    // v7.29 为了适配16k的模型 + 有了轮询/轮换key功能后可以无惧"429错误"。 *发送的单条内容越大，预估的误差也会增大
                    // 比例：600 => 100 ，要么保证GPT的回复空间(保证回复质量)，要么就故意报错激活自动重问功能。 *建议免费计划要使用2个账号的Key来轮询。
                    let temp_x = estimatedTokens / 6;

                    //通过“可用Key”的数量调整比例，更加激进，出错的概率也提高，目的是激活自动重问/自动修复
                    const good_key_count = this.apiCheckedData.filter(apidata => [0, 1, 2, 3].includes(apidata.level)).length;
                    if (good_key_count >= 3 || this.apiCheckedData.some(apikeydata => apikeydata.level === 0)) {
                        temp_x = estimatedTokens / 4;
                    } else if (good_key_count >= 2) {
                        temp_x = estimatedTokens / 5.3;
                    }

                    remainingTokens = temp_x >= 50 ? temp_x : 50;
                }
                //	console.log("max_tokens最终设为(微调，减小，降低错误发生率) : " + Math.ceil(remainingTokens));
                this.apiMaxTokens = Math.ceil(remainingTokens);
            },

            // 设置窗口 - 保存接口网址 API URL
            saveApiURL() {
                if (this.checkBusyStatus()) {
                    // 如果返回 true，正在发送或正在验证
                    return;
                }

                let notif = '';

                //不填写/保留空白，将恢复默认的官方接口
                if (this.inputApiURL.trim() == '') {

                    this.inputApiURL = 'https://api.openai.com/v1/chat/completions';
                    this.apiURL = this.inputApiURL;

                    //需要做些处理 改变当前状态 需要重新验证。
                    this.selectedApiURL = this.inputApiURL; //1 下拉框显示为“官方”/“GPT”
                    this.btnDisabledState_SaveApiURL = true; //2 保存按钮变为不可用
                    ////v7.10 逻辑更改，不再绑定API URL与API Key，哪怕API URL无效，也不改变按钮状态
                    ////v7.10 delete... this.inputapichange(true); // 3 重新验证API Key时 改变按钮等状态
                    notif = this.settingsLanguage == 'cn' ? '已重置为OpenAI官方接口' : 'The API ENDPOINT URL has been restored to the OpenAI link.';
                    this.showNotification(notif, 'bottom', '', 2500);
                } else {

                    // 1. 验证网址 开头只允许http:// 和 https:// 两种。
                    // 允许http：不了解接口规范，不排除私人搭建的某些http也能成功。不提醒，默认通过。
                    const urlRegex = /^(https?|http):\/\/[^\s/$.?#]+\.[^\s]*$/;
                    if (!urlRegex.test(this.inputApiURL.trim())) {
                        notif = this.settingsLanguage == 'cn' ? '非完整网址格式，请输入以 https:// 开头的完整网址后重新保存' : 'Please input a website address that starts with https:// and save again.';
                        this.showNotification(notif, 'bottom', '', 3800);
                        return;
                    }

                    // 2. 验证this.inputApiURL这个网址是否以/v1/chat/completions结尾，如果不是，则弹窗由用户确认是继续还是返回
                    if (!this.inputApiURL.trim().endsWith('/v1/chat/completions')) {
                        const msg = '\r\n\r\n';
                        const notif_cn = '本代码仅支持GPT-3.5或更新版本的GPT模型，对应的接口地址应以“/v1/chat/completions”结尾，如官方接口“https://api.openai.com/v1/chat/completions”。\r\n但是不排除第三方接口可能未按规范配置，请确认是否继续保存并使用该接口网址。 ';
                        const notif_en = 'The code only supports GPT-3.5 or newer versions of the model, and the API ENDPOINT URL should end in "/v1/chat/completions", like the official one "https://api.openai.com/v1/chat/completions". \r\nHowever, it is not ruled out that the current URL can be used, please confirm whether to continue saving?';
                        notif = this.settingsLanguage == 'cn' ? notif_cn : notif_en;
                        if (!confirm(notif)) {
                            return;
                        }
                    }

                    // 3. 保存
                    this.apiURL = this.inputApiURL.trim();

                    // 4. 需要做些处理 改变当前状态 需要重新验证
                    this.btnDisabledState_SaveApiURL = true; //1 保存按钮变为不可用
                    ////v7.10 逻辑更改，不再绑定API URL与API Key，哪怕API URL无效，也不改变按钮状态
                    ////v7.10 delete... this.inputapichange(true); // 2 重新验证API Key时 改变按钮等状态

                    // 5. 提醒用户先验证API Key
                    notif = this.settingsLanguage == 'cn' ? '保存成功' : 'Save successful';
                    this.showNotification(notif, 'bottom');
                }
            },
            // 设置窗口 - API URL下拉框  更新
            updateInputApiURL() {
                this.inputApiURL = this.selectedApiURL;
                this.saveApiURL(); //保存操作
            },
            // 设置窗口 - API URL输入框 更新
            inputApiURLChange() {
                this.selectedApiURL = ''; //重置下拉框为空，显示下拉箭头
                this.btnDisabledState_SaveApiURL = false; //保存按钮变为可用
            },


            //当前状态为"发送中"或"验证key中"时，需要禁止一些操作 Some operations should be prohibited when the current state is "sending" or "checking api key."
            checkBusyStatus(mode) {
                let tip = '';
                if (this.isSendingNow || this.isSendingNow_ForSmartMaxTokens) {
                    tip = this.settingsLanguage == 'cn' ? '正在发送消息，请等发送结束后再继续操作' : 'Sending message, please wait until finished before continuing.';
                    this.showNotification(tip, 'bottom', '', 2900);
                    return true;
                }
                if (this.isCheckingApiKeyNow) {
                    tip = this.settingsLanguage == 'cn' ? '正在验证 API Key，请等验证结束后再继续操作' : 'Verifying API Key, please wait until finished before continuing.';
                    this.showNotification(tip, 'bottom', '', 2900);
                    return true;
                }
                if (mode === 'ForChat' && this.localStorageEvent_chatDataList_update_start) {
                    tip = this.settingsLanguage == 'cn' ? '正在标签页之间同步数据，请1～2秒后再继续操作' : 'Synchronizing data between tabs, please wait 1 to 2 seconds before continuing.';
                    this.showNotification(tip, 'bottom', '', 3900);
                    return true;
                }
                return false;
            },

            // 设置窗口 - 头像 URL输入框
            inputImageUrlChange(role) {
                if (role == 'user') {
                    this.btnDisabledState_updateUserImageUrl = false;
                } else {
                    this.btnDisabledState_updateGPTImageUrl = false;
                }
            },
            // 设置窗口 - 头像下拉框
            updateSelectedImage(role) {
                let imageurl = this.selectedImageUrl;
                this.selectedImageUrl = ''; //复原

                //if (!imageurl.trim().startsWith('http://') && !imageurl.trim().startsWith('https://') && !imageurl.trim().startsWith('file:///'))
                //发现以上代码没有排除相对路径，当存在相对路径头像时，会产生无法切换头像的小bug。 目前SVG头像只有一种，也没必要判断。

                //当选择的是GPT的绿色SVG图像时
                if (imageurl == 'Change-GPT-Green-SVG') {
                    if (role == 'gpt') {
                        this.inputGPTImageUrl = this.settingsLanguage == 'cn' ? this.GPTGreenSVG_UrlText_cn : this.GPTGreenSVG_UrlText_en;
                    }
                } else if (imageurl == 'uploadimage') {
                    //上传图片 上传头像 upload image
                    this.openFileDialog_Avatar(role);
                    return;
                } else {
                    if (role == 'user') {
                        this.inputUserImageUrl = imageurl;
                    } else {
                        this.inputGPTImageUrl = imageurl;
                    }
                }
                this.updateAvatar(role);//保存 更新 save update
            },
            // 设置窗口 - 更新头像  shownotif：是否允许通知（当读取缓存时，不需要通知）
            updateAvatar(role, shownotif) {
                let imageUrl = '';
                let notif = '';
                let isShowNotif = shownotif === false ? shownotif : true;

                if (role == 'user') {
                    imageUrl = this.inputUserImageUrl.trim();
                } else {
                    imageUrl = this.inputGPTImageUrl.trim();
                }

                if (imageUrl == '') {
                    notif = this.settingsLanguage == 'cn' ? '图片的网址为空' : 'Image URL is empty';
                    if (isShowNotif) {
                        this.showNotification(notif, 'bottom');
                    }
                    return;
                }

                //如果是GPT绿色的SVG头像。手动更新
                if (role != 'user' && (imageUrl == this.GPTGreenSVG_UrlText_cn || imageUrl == this.GPTGreenSVG_UrlText_en)) {
                    this.gptAvatarURL = ""; //imgUrl清空
                    this.isgptGreenSvgShow = true; //显示<svg> 并隐藏<img>
                    this.btnDisabledState_updateGPTImageUrl = true;

                    notif = this.settingsLanguage == 'cn' ? '已更换头像' : 'Avatar has been updated';
                    if (isShowNotif) {
                        this.showNotification(notif, 'bottom');
                    }
                    return;
                }

                const img = new Image();
                notif = this.settingsLanguage == 'cn' ? '更换中...' : 'Updating...';
                if (isShowNotif) {
                    this.showNotification(notif, 'bottom', '', 2900);
                }


                img.onload = () => {
                    // 图片加载成功，更新头像
                    if (role == 'user') {
                        this.userAvatarURL = this.inputUserImageUrl;
                        this.btnDisabledState_updateUserImageUrl = true;
                    } else {
                        this.gptAvatarURL = this.inputGPTImageUrl;
                        this.isgptGreenSvgShow = false; //隐藏<svg> 并显示<img>
                        this.btnDisabledState_updateGPTImageUrl = true;
                    }
                    notif = this.settingsLanguage == 'cn' ? '已更换头像' : 'Avatar has been updated';

                    if (isShowNotif) {
                        //必须延迟，否则当url不变时，瞬间激活onload事件，此时部分手机上会出现底部通知消息会出现横移/闪跳的情况。
                        setTimeout(() => {
                            this.showNotification(notif, 'bottom');
                        }, 1250);
                    }
                };

                img.onerror = () => {
                    if (role == 'user') {
                        this.btnDisabledState_updateUserImageUrl = false;
                    } else {
                        this.btnDisabledState_updateGPTImageUrl = false;
                    }
                    // 图片加载失败，提示用户
                    let message = this.settingsLanguage == 'cn' ? '无法使用该图片作为头像，请选择其他图片' : 'Unable to use this image as an avatar. Please select another image.';

                    if (isShowNotif) {
                        //清除定时器 然后手动提前隐藏通知
                        clearTimeout(this.notificationTimeoutId);
                        setTimeout(() => {
                            this.isShowNotification = false;
                            this.notificationTimeoutId = -1;
                        }, 600); // 延迟，先确保第一个通知顺利的弹出，然后再控制手动关闭通知。 不延迟的话，此时this.isShowNotification还可能为false（尚未弹出）。

                        //延迟0.8秒，目的：控制顺序，先确保第一个通知顺利的弹出，然后定时手动隐藏通知，最后再弹窗警告。
                        setTimeout(() => {
                            alert(message);
                        }, 800);
                    } else {
                        if (!(role == 'user' && this.userAvatarURL === '/img/me-bili.jpg')) { //在国内，未打开V. P_N，默认的"bili头像"大概率会加载失败，所以排除
                            // 恢复本地存储的头像（网址、路径、上传的头像），遇到错误时
                            const rolestr = this.settingsLanguage == 'cn' ? (role == 'user' ? '[用户头像]' : '[GPT头像]') : (role == 'user' ? '[User Avatar]' : '[GPT Avatar]');
                            notif = this.settingsLanguage == 'cn' ? rolestr + ' 未能加载' : rolestr + ' failed to load';
                            const delaytime = this.isShowNotification ? 5000 : 200;
                            setTimeout(() => {
                                this.showNotification(notif, 'bottom', '', 2000);
                            }, delaytime);
                        }
                    }
                };

                img.src = imageUrl;
            },

            //添加上传头像的input控件（删除 > 添加 > 加事件）
            addInput_FileDialog_Avatar(role) {
                const isInputExit = role == 'user' ? document.getElementById('inputFileAvatar-user') : document.getElementById('inputFileAvatar-gpt');
                const dialogSettings = document.getElementById("dialog-settings");
                const div_userAvataror = document.getElementById("div-upload-userAvataror");
                const div_gptAvataror = document.getElementById("div-upload-gptAvataror");
                const div_gptAvataror_SVG = document.getElementById("div-upload-gptAvataror-SVG");

                //删除
                if (isInputExit) { //input已存在。目前的逻辑，应该每次都应该要存在
                    if (role == 'user') {
                        div_userAvataror.onclick = null; // 移除之前的点击事件
                    } else {
                        div_gptAvataror.onclick = null; // 移除之前的点击事件
                        div_gptAvataror_SVG.onclick = null; // 移除之前的点击事件
                    }
                    isInputExit.onchange = null;
                    dialogSettings.removeChild(isInputExit);//每次都移除
                }

                //添加
                const input = document.createElement('input');
                input.type = 'file';
                input.accept = 'image/*';
                input.style.display = 'none'; //隐藏的，只有通过下拉框选项或点击设置中的头像才能打开选择图片的对话框。
                //input.style.zIndex = 999999; //调试的时候需要
                input.id = role == 'user' ? 'inputFileAvatar-user' : 'inputFileAvatar-gpt';

                dialogSettings.appendChild(input);

                //DIV事件
                if (role == 'user') {
                    div_userAvataror.onclick = () => input.click();  // 修改div的点击事件
                } else {
                    div_gptAvataror.onclick = () => input.click(); // 修改div的点击事件
                    div_gptAvataror_SVG.onclick = () => input.click(); // 修改div的点击事件
                }


                //事件 onchange
                input.onchange = (event) => {
                    let notif = '';
                    let notiftype = 'bottom';
                    if (!event.target || !event.target.files || event.target.files.length == 0) {
                        return;
                    }

                    const file = event.target.files[0]; //blob
                    const filesize = file.size / 1024;
                    const url = URL.createObjectURL(file); //blob url
                    if (role == 'user') {
                        //判断图片大小。 若大于350k，提醒不存到浏览器缓存，刷新后头像将被清除
                        if (filesize > 350) {
                            // 图片大小超过了350k
                            const notif_cn = '上传的图片大小超过了 350KB (0.35MB)，因此无法保存到浏览器本地存储 (localStorage) 中。这不会影响使用，只是在刷新或重新打开网页后，[用户头像]会失效，需要重新上传。\r\n\r\n  • localStorage容量有限，所以限制只存储大小在 350KB (0.35MB) 以内的1张图片，且只能是[用户头像]。\r\n  • 上传的图片仅会缓存在您的浏览器中，并不会通过任何方式对外传输。';
                            const notif_en = "The image you uploaded exceeds 350KB (0.35MB) in size and cannot be stored in the browser's local storage (localStorage). This will not affect usage, but the [User Avatar] will need to be re-uploaded when the page is reopened or refreshed.\r\n\r\n  • Due to limited capacity, localStorage restricts the storage of only one image under 350KB (0.35MB) and it can only be the user avatar. \r\n  • The uploaded images will only be stored in your browser cache and will not be transmitted over the network.";
                            notif = this.settingsLanguage == 'cn' ? notif_cn : notif_en;
                            notiftype = 'center';
                        } else {
                            //将头像存储到localStorage只有一种方法，就是点击用户的“下拉框”或“头像”，然后上传。  如果是通过点击gpt的“下拉框”或“头像”上传，然后复制blob url保存到用户头像的，这种情况不会存储到localStorage，刷新后失效。

                            // 图片大小在350k以内
                            this.userAvatarUp_blobUrl = url; //(user & blob <= 350)
                            this.userAvatarUp_blob = file; //(user & blob <= 350)
                            // 文件符合要求，将图片保存到LocalStorage
                            const reader = new FileReader();
                            reader.readAsDataURL(file);
                            reader.onload = () => {
                                this.userAvatarUp_base64 = reader.result; //(user & blob <= 350)
                            };

                            //提示
                            notif = this.settingsLanguage == 'cn' ? '上传成功（图片仅会缓存在您的浏览器中，并不会通过任何方式对外传输）' : 'Upload successful (the image will only be stored in the browser cache and will not be transmitted over the network).';
                            notiftype = 'bottom';
                        }
                    } else {
                        const notif_cn = '上传的[GPT头像]，不会存储到浏览器本地存储 (localStorage) 中，在刷新或重新打开网页后，上传的[GPT头像]将会失效，需要重新上传。\r\n\r\n  • localStorage容量有限，所以限制只存储大小在 350KB (0.35MB) 以内的1张图片，且只能是[用户头像]。\r\n  • 上传的图片仅会缓存在您的浏览器中，并不会通过任何方式对外传输。';
                        const notif_en = "The uploaded [GPT Avatar] will not be stored in the browser's local storage (localStorage). After reopening or refreshing the page, the uploaded [GPT Avatar] will become invalid and need to be re-uploaded.\r\n\r\n  • Due to limited capacity, localStorage restricts the storage of only one image under 350KB (0.35MB) and it can only be the [User Avatar]. \r\n  • The uploaded images will only be stored in your browser cache and will not be transmitted over the network.";
                        notif = this.settingsLanguage == 'cn' ? notif_cn : notif_en;
                        notiftype = 'center';
                    }

                    // 将文件地址赋值给imageUrl，并执行updateAvatar方法
                    if (role == 'user') {
                        this.inputUserImageUrl = url;
                    } else {
                        this.inputGPTImageUrl = url;
                    }

                    this.updateAvatar(role, true);//更新头像 true = show notif.

                    //（删旧的 > 加新的 > 加新的事件）每次上传后，立马换个新的上传控件（大概是因为input上传控件的特殊性吧，网上教程是这么做的，GPT也是推荐手动添加&删除）
                    this.addInput_FileDialog_Avatar(role);

                    //延迟执行 之前会出现“更换中...”和“已更换头像”
                    setTimeout(() => {
                        const bottom_time = this.settingsLanguage == 'cn' ? 5200 : 6500;
                        const center_title = this.settingsLanguage == 'cn' ? '提醒' : 'Tip'; // 提醒 信息
                        this.showNotification(notif, notiftype, center_title, bottom_time);
                    }, 2600);//毫秒，ms
                };

                return input;
            },

            //（下拉框 调用）上传图片 上传头像 upload image
            openFileDialog_Avatar(role) {

                const isInputExit = role == 'user' ? document.getElementById('inputFileAvatar-user') : document.getElementById('inputFileAvatar-gpt');
                if (isInputExit) {
                    isInputExit.click();
                } else {
                    const input = this.addInput_FileDialog_Avatar(role);
                    isInputExit.click();
                }

                let isIOS = /iPad|iPhone|ios|iPod/.test(navigator.userAgent) && !window.MSStream; // 判断是否为 iOS 设备
                let isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); // 判断是否为 Safari 内核
                if (isIOS || isSafari) {
                    //iphone或 Safari(iphone/mac) 需要点击头像才能上传
                    const notif = this.settingsLanguage == 'cn' ? 'Safari浏览器(Mac/iPhone)与iPhone全系列机型，仅支持通过点击右侧头像来上传图片' : 'For Safari (Mac/iPhone) and iPhone users, uploading images is only supported by clicking the "Avatar" on the right-hand side.';
                    const notiftime = this.settingsLanguage == 'cn' ? 5500 : 7300;
                    this.showNotification(notif, 'bottom', '', notiftime);
                }
            },
            // 设置窗口 - 更换语言 中英文切换
            changeLanguage() {

                debugger;

                //更新原生全局变量 window.currentlang  当前语言 Current Language
                window.currentlang = this.settingsLanguage;

                //btn - 发送 Send
                if (this.sentext == '先验证API' || this.sentext == 'Check API First') {
                    this.sentext = this.settingsLanguage == 'cn' ? '先验证API' : 'Check API First';
                } else if (this.sentext == '发送' || this.sentext == 'Send') {
                    this.sentext = this.settingsLanguage == 'cn' ? '发送' : 'Send';
                } else if (this.sentext == 'API Key错误' || this.sentext == 'API Key Error') {
                    this.sentext = this.settingsLanguage == 'cn' ? 'API Key错误' : 'API Key Error';
                } else if (this.sentext == '重新验证API' || this.sentext == 'Recheck API') {
                    this.sentext = this.settingsLanguage == 'cn' ? '重新验证API' : 'Recheck API';
                } else if (this.sentext == '请重发' || this.sentext == 'Please Retry') {
                    this.sentext = this.settingsLanguage == 'cn' ? '请重发' : 'Please Retry';
                }

                //btn - 验证 Check API Key
                // if ( this.apibtntext == '已验证' || this.apibtntext == 'Checked' ) {
                // 	this.apibtntext = this.settingsLanguage == 'cn' ? '已验证' : 'Checked';
                // }else if ( this.apibtntext == '<< 验证' || this.apibtntext == '<< Check' ) {
                // 	this.apibtntext = this.settingsLanguage == 'cn' ? '<< 验证' : '<< Check';
                // }

                //input - GPT头像 url text
                // if ( this.inputGPTImageUrl.trim() == this.GPTGreenSVG_UrlText_cn || this.inputGPTImageUrl.trim() == this.GPTGreenSVG_UrlText_en ) {
                // 	this.inputGPTImageUrl = this.settingsLanguage == 'cn' ? this.GPTGreenSVG_UrlText_cn : this.GPTGreenSVG_UrlText_en;
                // }

            },
            // 设置窗口 - 上下文模式
            changeContextualMode() {
                //11111111

            },
            // 设置窗口 - 改变字体大小
            changeStyle_FontSize() {

            },
            // 设置窗口 - 显示 隐藏 时间 微信样式
            changeStyle_Time_WechatStyle() {
                //1、add_span_WeChatStyleDatetime() 手动添加新的微信时间， 添加时判断是否显示 + 判断样式(与消息时间有关联)  +（*与分割线的关联 改样式写在分割线的代码里）
                //2、changeStyle_Time_WechatStyle()  改变之前的微信时间的显示状态 + 分割线的样式  +（*与消息时间的关联 改样式写在消息时间的代码里）
                //应该没问题 。 手动添加，无法绑定变量

                let newcontexthrbottom = this.settingsTime_WechatStyle ? '20px' : '33px';
                document.querySelectorAll('.newcontext-hr').forEach(function (element) {
                    element.style.marginBottom = newcontexthrbottom;
                });
            },
            // 设置窗口 - 显示 隐藏 消息时间
            changeStyle_Time() {
                //1、非手动添加。显示状态写在<div v-if="x.my" class="msgdatetimediv" :style="{ display: settingsTime_Message == 1 ? 'none' : 'block' }" >
                // class可以绑定 style可以绑定 目前使用变量控制新旧消息时间的显示状态
                //2、changeStyle_Time() 仅改变样式。 修改之前的消息时间的样式： 第3种模式增大间距需要改变样式 + 当微信时间显示时，还需要改变微信时间的样式
                //3、漏洞：缺少新消息时间判断样式的代码
                //   一、userinfo和aiinfo 添加绑定 marginTop : settingsTime_Message == 3 ? '27px' : '17px'  ,绑定后可注释删除changeStyle_Time()里的对应代码。
                //   二、user消息时间 和 微信时间(只会存在user这边) 是同时产生的，新增微信时间的样式判断已经写在add_span_WeChatStyleDatetime()，无需再做处理。
                //   changeStyle_Time() 只需要修改之前的微信时间样式即可。

            },
            // 设置窗口 - 显示 隐藏 分割线
            changeStyle_DividerLine() {
                //1、changeStyle_DividerLine() 修改之前的分割线的显示状态 不改变样式
                //2、add_hr_newRoundContext() 手动添加新分割线时 1判断是否显示新分割线 2判断新分割线的样式（受微信样式时间的影响）*分割线与消息时间 之间无样式关联
                //3、changeStyle_Time_WechatStyle() 只有当微信样式时间的状态发生改变时，才会改变之前分割线的样式
                // 应该没问题 手动添加，无法绑定变量

                let displayStyle = this.settingsDividerLine ? 'block' : 'none';
                document.querySelectorAll('.newcontext-hr').forEach(function (element) {
                    element.style.display = displayStyle;
                });
            },
            // 设置窗口 - 快捷键 功能键 For PC
            handleShortcut(event) {
                var key = event.key || event.keyCode || event.which;
                if (key === 'F1' || key === 112) {
                    event.preventDefault(); // 防止浏览器默认行为
                    // 清空
                    if (!this.settingsShortcut_Clear) {
                        return;
                    }//未开启快捷键时 返回
                    if (this.btnDisabledState_Clear) {
                        return;
                    }//清空按钮不可用时 返回

                    if (this.isShowDialog_Settings) {
                        return;
                    }//设置窗口打开时 返回
                    if (this.isShowDialog_PromptGenerator) {
                        return;
                    }//提示词窗口打开时 返回
                    if (this.isShowNotification) {
                        return;
                    }//center / bottom 两种通知消息时 都返回 *底部通知时其实可以允许使用快捷键，但时长较短，仅3秒，干脆也禁了
                    if (this.isShowDialog_ChatList) {
                        return;
                    }//对话列表窗口打开时 返回
                    if (this.isShowDialog_AutoKeyRotation) {
                        return;
                    }//自动轮换API Key窗口打开时 返回


                    //其他弹窗 1111111。压缩上下文

                    this.clearContext(); //清空
                }

                if (key === 'F2' || key === 113) {
                    event.preventDefault(); // 防止浏览器默认行为
                    // 验证API Key
                    if (!this.settingsShortcut_CheckApiKey) {
                        return;
                    }//未开启快捷键时 返回
                    if (this.btnDisabledState_CheckAPI) {
                        return;
                    }//验证按钮不可用时 返回

                    this.checkAPIbtn(); //验证
                }

                if (key === 'F3' || key === 114) {
                    event.preventDefault(); // 防止浏览器默认行为
                    // 撤销
                    if (!this.settingsShortcut_Undo) {
                        return;
                    }//未开启快捷键时 返回
                    if (this.btnDisabledState_Undo) {
                        return;
                    }//撤销按钮不可用时 返回

                    if (this.isShowDialog_Settings) {
                        return;
                    }//设置窗口打开时 返回
                    if (this.isShowDialog_PromptGenerator) {
                        return;
                    }//提示词窗口打开时 返回
                    if (this.isShowNotification) {
                        return;
                    }//center / bottom 两种通知消息时 都返回
                    if (this.isShowDialog_ChatList) {
                        return;
                    }//对话列表窗口打开时 返回
                    if (this.isShowDialog_AutoKeyRotation) {
                        return;
                    }//自动轮换API Key窗口打开时 返回
                    //其他弹窗 1111111。压缩上下文

                    this.undo();//撤销
                }

                if (key === 'F4' || key === 115) {
                    event.preventDefault(); // 防止浏览器默认行为
                    // 重问
                    if (!this.settingsShortcut_Retry) {
                        return;
                    }//未开启快捷键时 返回
                    if (this.btnDisabledState_Undo) {
                        return;
                    }//与撤销相同。 撤销按钮不可用时 返回

                    if (this.isShowDialog_Settings) {
                        return;
                    }//设置窗口打开时 返回
                    if (this.isShowDialog_PromptGenerator) {
                        return;
                    }//提示词窗口打开时 返回
                    if (this.isShowNotification) {
                        return;
                    }//center / bottom 两种通知消息时 都返回
                    if (this.isShowDialog_ChatList) {
                        return;
                    }//对话列表窗口打开时 返回
                    if (this.isShowDialog_AutoKeyRotation) {
                        return;
                    }//自动轮换API Key窗口打开时 返回
                    //其他弹窗 1111111。压缩上下文

                    this.retry();//重问
                }
            },
            //判断是否存在代码块的反引号，是否有连续3个或以上的反引号（通常只会遇到3～5个的情况），找存在的最长的，然后返回长度加1的连续反引号。 连续长度多少决定了循环次数，限制100。
            //目的是特殊情况下需要利用代码块来包裹整段提示词内容，如果没有足够长度的反引号，markdown上的排版可能会很乱
            findMaxBackticks(str) {
                let pattern, regex;
                // Check for 5 backticks
                pattern = "`{5}";
                regex = new RegExp(pattern);
                if (regex.test(str)) {
                    // Check for more than 5 backticks
                    for (let i = 6; i <= 100; i++) {
                        pattern = "`".repeat(i);
                        regex = new RegExp(pattern);
                        if (!regex.test(str)) {
                            return "`".repeat(i);
                        }
                    }
                } else {
                    // Check for 4, 3 backticks
                    for (let i = 4; i >= 3; i--) {
                        pattern = "`".repeat(i);
                        regex = new RegExp(pattern);
                        if (regex.test(str)) {
                            return "`".repeat(i + 1);
                        }
                    }
                }
                return '';
            },
            // 设置窗口 - 添加提示词到聊天记录中（ChatHistory.txt）
            addSystemPromptToChatHistory(head_cn, head_en) {
                //提示词 使用两种markdown语法：
                //		1、引用。当没有空行、没有```代码块时，使用"> "引用语法。提示词整段显示为引用，与聊天记录区分开。
                //		2、代码块。当提示词中存在空行 、```代码块时，导出聊天记录的markdown视图会显得很乱，此时使用代码块语法来包裹整段提示词，但提示词中本身很能存在“```”甚至更长的反引号，这会导致包裹失败，所以要用更长的反引号。
                //		  ...也可以直接写死，比如直接用10个反引号来包裹。但是若将导出的记录再作为提示词使用的话，下次导出聊天记录时就无法包裹了（反引号长度相同）。
                const regex = /\r?\n\r?\n/;  //正则，匹配连续两个换行符(即匹配是否存在空行，支持Unix、Windows的换行符)
                let hasMultipleLines = regex.test(this.gptSystemPromptReadOnly.trim()); //是否存在空行  包含空行时无法支持引用语法"> "
                let backticks = this.findMaxBackticks(this.gptSystemPromptReadOnly.trim()); //提示词中可能存在的最长的连续反引号。 返回“长度加1的连续反引号”
                hasMultipleLines = backticks != '' ? true : hasMultipleLines; //如果含有3个或以上的反引号，同样不能使用"引用"语法
                backticks = backticks == '' ? '````' : backticks; //更新变量，一种情况会用到：当提示词(不包含代码块&&包含空行)的时候，此时用于包裹提示词的代码块需要3个反引号，3个就够，但还是用4个吧

                //v6.01 一个开关，chatHistoryPromptOnlyUseBackticks=true 全部使用反引号，chatHistoryPromptOnlyUseBackticks=false 自动判断使用引用或者反引号
                //v6.01 When chatHistoryPromptOnlyUseBackticks=true, only backtick syntax will be used. When chatHistoryPromptOnlyUseBackticks=false, the system will automatically determine whether to use quote syntax or backtick syntax.
                if (this.chatHistoryPromptOnlyUseBackticks) {
                    hasMultipleLines = true;
                }

                let addMarkdownSyntaxStart = hasMultipleLines ? ('\r\n' + backticks + '\r\n') : '\r\n> ';  // (> 后面有个空格)
                let addMarkdownSyntaxEnd = hasMultipleLines ? ('\r\n' + backticks + '\r\n\r\n') : '\r\n\r\n';
                let nullprompt = '';  //提示词为空的情况
                if (this.gptSystemPromptReadOnly.trim() == '') {
                    nullprompt = this.settingsLanguage == 'cn' ? ': 空' : ': None';
                    addMarkdownSyntaxStart = '\r\n';
                    addMarkdownSyntaxEnd = '\r\n';
                }
                let txt_lang = this.settingsLanguage == 'cn' ? (head_cn + nullprompt + addMarkdownSyntaxStart) : (head_en + nullprompt + addMarkdownSyntaxStart);
                //1.提示词为空。  2.提示词含有空行，但不含有```代码块。  3.提示词含有```代码块（连续3个或以上的反引号为markdown的代码块，3个以下是"行代码，单行"）
                // 也可以直接考虑只使用“代码块”包裹，而不使用“引用”。“引用”出现失败的概率也不小，测试中发现不同的在线markdown编辑器的差异还蛮大。
                this.chathistory += (txt_lang + this.gptSystemPromptReadOnly.trim() + addMarkdownSyntaxEnd);
            },
            // 设置窗口 - 恢复默认 API设置
            restoreDefault_APISettings() {
                //if (this.checkBusyStatus()) {
                //	// 如果返回 true，正在发送或正在验证
                //	return;
                //}

                this.openSmartMaxTokens = true; //默认开启，localStorage无此变量

                this.apitemperature = JSON.parse(JSON.stringify(this.userVariables_default.apitemperature)); //这里其实不需要双向绑定
                this.apiMaxTokens = JSON.parse(JSON.stringify(this.userVariables_default.apiMaxTokens));
                this.apiGPTModel = JSON.parse(JSON.stringify(this.userVariables_default.apiGPTModel));

                const notif = this.settingsLanguage == 'cn' ? 'API设置 已恢复默认值' : 'API Settings have been reset to default values.';
                const time = this.settingsLanguage == 'cn' ? 2500 : 3300;
                this.showNotification(notif, 'bottom', '', time);
            },
            // 设置窗口 - 恢复默认 API设置 专业
            restoreDefault_ProfessionalAPISettings() {
                if (this.checkBusyStatus()) {
                    // 如果返回 true，正在发送或正在验证
                    return;
                }

                //同步saveApiURL中关于保存默认url的步骤
                this.inputApiURL = 'https://api.openai.com/v1/chat/completions'; //输入框显示的api url
                this.apiURL = JSON.parse(JSON.stringify(this.inputApiURL)); // 真正的的api url
                this.selectedApiURL = JSON.parse(JSON.stringify(this.inputApiURL)); //下拉框显示为“官方”/“GPT”
                ////v7.10 逻辑更改，不再绑定API URL与API Key，哪怕API URL无效，也不改变按钮状态。
                ////v7.10 delete... this.inputapichange(true); // 重新验证API Key时 改变按钮等状态
                this.btnDisabledState_SaveApiURL = true; //保存按钮状态改为不可用

                this.apiTopP = JSON.parse(JSON.stringify(this.userVariables_default.apiTopP));//这里其实不需要双向绑定
                this.apiPresencePenalty = JSON.parse(JSON.stringify(this.userVariables_default.apiPresencePenalty));
                this.apiFrequencyPenalty = JSON.parse(JSON.stringify(this.userVariables_default.apiFrequencyPenalty));

                const notif = this.settingsLanguage == 'cn' ? 'API专业设置 已恢复默认值' : 'Professional API Settings have been reset to default values.';
                const time = this.settingsLanguage == 'cn' ? 2500 : 3300;
                this.showNotification(notif, 'bottom', '', time); //this.inputapichange 会弹第二个通知
            },
            // 设置窗口 - 恢复默认 常规设置
            restoreDefault_GeneralSettings() {
                if (this.checkBusyStatus()) {
                    // 如果返回 true，正在发送或正在验证
                    return;
                }

                this.settingsFontSize = JSON.parse(JSON.stringify(this.userVariables_default.settingsFontSize));//这里其实不需要双向绑定
                this.changeStyle_FontSize();

                this.settingsTime_WechatStyle = JSON.parse(JSON.stringify(this.userVariables_default.settingsTime_WechatStyle));
                this.changeStyle_Time_WechatStyle();

                this.settingsTime_Message = JSON.parse(JSON.stringify(this.userVariables_default.settingsTime_Message));
                this.changeStyle_Time();

                this.settingsDividerLine = JSON.parse(JSON.stringify(this.userVariables_default.settingsDividerLine));
                this.changeStyle_DividerLine();

                // *this.chatVariables_default
                this.settingsContextualMode = JSON.parse(JSON.stringify(this.chatVariables_default.settingsContextualMode)); // ***ChatData
                this.changeContextualMode();

                this.settingsShortcut_CheckApiKey = JSON.parse(JSON.stringify(this.userVariables_default.settingsShortcut_CheckApiKey));
                this.settingsShortcut_Clear = JSON.parse(JSON.stringify(this.userVariables_default.settingsShortcut_Clear));
                this.settingsShortcut_Undo = JSON.parse(JSON.stringify(this.userVariables_default.settingsShortcut_Undo));
                this.settingsShortcut_Retry = JSON.parse(JSON.stringify(this.userVariables_default.settingsShortcut_Retry));

                const notif = this.settingsLanguage == 'cn' ? '常规设置 已恢复默认设置。 *语言和头像的设置会被保留，需手动修改' : 'General Settings have been reset to default values. *Language and avatar settings will be retained and need to be manually modified. ';
                const time = this.settingsLanguage == 'cn' ? 5000 : 6500;
                this.showNotification(notif, 'bottom', '', time);
            },

            // 设置窗口 - 点击打开对话列表
            clickOpenChatList() {
                this.isShowDialog_ChatList = true;
                const notif = this.settingsLanguage == 'cn' ? '在聊天界面中，点击任意头像可快速打开对话列表' : 'In the chat interface, clicking on any avatar quickly opens the chat list.';
                this.showNotification(notif, 'bottom', '', 6500);
            },
            // 设置窗口 - 点击打开提示词窗口
            clickOpenPromptGenerator() {
                this.isShowDialog_PromptGenerator = true;
                const notif = this.settingsLanguage == 'cn' ? '在聊天界面中，点击斜杆 (/) 可快速打开提示词窗口' : 'In the chat interface, clicking on the slash (/) quickly opens the Prompt Generator.';
                this.showNotification(notif, 'bottom', '', 6500);
            },
            // 设置窗口 - 导出聊天记录下拉框
            changeSelectedExportChatHistory() {
                let exportType = this.selectedExportChatHistory;
                this.selectedExportChatHistory = ''; //复原
                let notif = this.settingsLanguage == 'cn' ? '正在导出 { #' + this.chatid + ' } 的聊天记录' : 'Exporting chat history of { #' + this.chatid + ' }';
                this.showNotification(notif, 'bottom', '', 4000);

                if (exportType == 'ChatHistory_Single_MD') {
                    //单轮记忆 (.md)
                    this.ExportData_SingleRound(true, true, false, 'markdown');
                } else if (exportType == 'ChatHistory_Single_TXT') {
                    //单轮记忆 (.txt)
                    this.ExportData_SingleRound(true, true, false, 'plain');
                } else if (exportType == 'ChatHistory_ALL_MD') {
                    //全部 (.md)
                    this.ExportData(this.chathistory, this.exportTXTFileName, 'markdown');
                } else if (exportType == 'ChatHistory_ALL_TXT') {
                    //全部 (.txt)
                    this.ExportData(this.chathistory, this.exportTXTFileName, 'plain');
                }
            },


            // 设置窗口 - 数据管理Data Management 缓存localStorage 删除数据 Delete Data
            changeSelectedDeleteLocalStorageData() {
                if (this.checkBusyStatus('ForChat')) {
                    this.selectedDeleteLocalStorageData = ''; //复原
                    //底部会出现通知tooltips。 如果返回 true， 正在发送 或 正在验证 或 正在标签页间同步数据
                    return;
                }
                let notif = '';
                let notiftime = 2000; //通知显示的时间
                let delaytime = 0;//延迟时间
                if (!this.isLocalStorageSupported) {
                    //浏览器当前不支持localStorage时
                    notif = this.settingsLanguage == 'cn' ? '正在尝试强制清除浏览器本地存储(localStorage)...' : 'Forcefully deleting browser local storage in progress...';
                }
                if (!this.isLocalStorageAllowed) {
                    //不允许缓存时
                    notif = this.settingsLanguage == 'cn' ? '正在尝试强制清除浏览器本地存储(localStorage)...' : 'Forcefully deleting browser local storage in progress...';
                }
                if (notif !== '') {
                    this.showNotification(notif, 'bottom', '', 3500);
                    delaytime = 2500;
                }
                notif = '';
                let typeStr = this.selectedDeleteLocalStorageData;
                this.selectedDeleteLocalStorageData = ''; //复原

                try {
                    if (typeStr === 'DeleteAllData') {
                        //'1) 一键删除所有数据', type: 'DeleteAllData'
                        //v7.17 不能用localStorage.clear()，电脑本地运行，file协议所有网站共用一个localStorage，clear()会删除所有本地允许的网站的缓存数据
                        //改为分别删除4项数据 //先删1次
                        localStorage.removeItem('chatDataList');
                        localStorage.removeItem('apikeyData');
                        localStorage.removeItem('promptsData');
                        localStorage.removeItem('userSettings');

                        notif = this.settingsLanguage == 'cn' ? '已删除所有数据' : 'All data has been deleted';

                        //1 List
                        this.chatDataList = [];//界面中删除List
                        this.chatList_addNewChatData(true);//添加新对话
                        //2 API Key
                        this.setVariablesToThis(this.apikeyData_default, this.apikeyData_default, true);
                        //this.inputapichange(false); 重复的，后面会执行
                        this.lastAPIKeyInputText = '';
                        this.lastAPIKeyInputText_Available = '';
                        if (this.defaultAPIKey !== '' || this.default_apiKeyArray_WrittenInTheCode.length > 0) {
                            notif += this.settingsLanguage == 'cn' ? '。（密钥: 部分写在代码里的 API Key 无法被删除，刷新后会恢复）' : '. (API keys: Some API Keys written in the code cannot be deleted and will be restored after refreshing.)';
                            notiftime = 7000;
                        }
                        //3 Prompts
                        this.setVariablesToThis(this.promptsData_default, this.promptsData_default, true);
                        this.btnDisabledState_SetSystemPrompt = false;
                        //4 User Settings
                        this.userAvatarUp_base64 = ''; //清空
                        this.userAvatarUp_blob = null; //清空
                        this.userAvatarUp_blobUrl = ''; //清空
                        this.inputUserImageUrl = '/img/me-bili.jpg'; //恢复默认头像url
                        this.updateAvatar('user', false);//刷新头像 false=Don't show notif.
                        this.inputGPTImageUrl = this.settingsLanguage == 'cn' ? this.GPTGreenSVG_UrlText_cn : this.GPTGreenSVG_UrlText_en;
                        this.updateAvatar('gpt', false);

                        this.setVariablesToThis(this.userVariables_default, this.userVariables_default, true); //*userVariables 变量
                        this.settingsContextualMode = JSON.parse(JSON.stringify(this.chatVariables_default.settingsContextualMode));//属于chatdata的变量，不属于userVariables。需要手动设置
                        this.openSmartMaxTokens = true; //localStorage无此变量。默认开启，需要手动设置。
                        this.changePageScaling(false);//需要在changeSimpleMode之前
                        this.changeLanguage();
                        this.changeSimpleMode(false);
                        this.inputapichange(false); // 重新验证API Key时 改变按钮等状态
                        this.btnDisabledState_SaveApiURL = true; //保存按钮状态改为不可用
                        this.changeStyle_FontSize();
                        this.changeStyle_Time_WechatStyle();
                        this.changeStyle_Time();
                        this.changeStyle_DividerLine();
                        this.changeContextualMode();

                        //最后再删一次  //不能使用localStorage.clear();
                        //改为分别删除4项数据 //先删1次
                        localStorage.removeItem('chatDataList');
                        localStorage.removeItem('apikeyData');
                        localStorage.removeItem('promptsData');
                        localStorage.removeItem('userSettings');
                    } else if (typeStr === 'DeleteChatDataList') {
                        //'2) 删除[对话]数据', type: 'DeleteChatDataList'
                        this.chatDataList = [];//先在界面中删除List
                        localStorage.removeItem('chatDataList');//之后在缓存中删除List
                        this.chatList_addNewChatData(true);//添加新对话
                        notif = this.settingsLanguage == 'cn' ? '已删除所有对话数据' : 'All chat data has been deleted';
                    } else if (typeStr === 'DeleteAPIKeyData') {
                        //'3) 删除[密钥 API Key]数据', type: 'DeleteAPIKeyData'
                        localStorage.removeItem('apikeyData');
                        //根据变量列表和默认值 还原默认变量
                        this.setVariablesToThis(this.apikeyData_default, this.apikeyData_default, true);
                        // API Key 改变按钮等状态
                        this.inputapichange(false);
                        this.lastAPIKeyInputText = '';
                        this.lastAPIKeyInputText_Available = '';
                        //this.defaultAPIKey 这个不删，用于记录写在代码的单个Key（this.api)。
                        notif = this.settingsLanguage == 'cn' ? '已删除密钥 (API Key) 数据' : 'API Keys data has been deleted';
                        if (this.defaultAPIKey !== '' || this.default_apiKeyArray_WrittenInTheCode.length > 0) {
                            notif += this.settingsLanguage == 'cn' ? '。 部分写在代码里的 API Key 无法被删除，刷新后会恢复' : '. Some API Keys written in the code cannot be deleted and will be restored after refreshing.';
                            notiftime = 6000;
                        }
                    } else if (typeStr === 'DeletePromptsData') {
                        //'4) 删除[提示词]数据', type: 'DeletePromptsData'
                        localStorage.removeItem('promptsData');
                        //根据变量列表和默认值 还原默认变量
                        this.setVariablesToThis(this.promptsData_default, this.promptsData_default, true);
                        //保存提示词按钮 设为可用 *针对之前保存过系统提示词的情况，保存后变为不可用。此时按钮需要变为可用
                        this.btnDisabledState_SetSystemPrompt = false;
                        notif = this.settingsLanguage == 'cn' ? '已删除提示词数据' : 'Prompts data has been deleted';
                    } else if (typeStr === 'DeleteUploadedUserAvatar') {
                        //'5) 删除[上传的用户头像]', type: 'DeleteUploadedUserAvatar'
                        this.userAvatarUp_base64 = ''; //清空
                        this.userAvatarUp_blob = null; //清空
                        this.userAvatarUp_blobUrl = ''; //清空
                        this.inputUserImageUrl = '/img/me-bili.jpg'; //恢复默认头像url
                        this.updateAvatar('user', false);//刷新头像 false=Don't show notif.
                        this.localStorage_SaveData(null, 'userSettings');//保存 更新localStorage
                        notif = this.settingsLanguage == 'cn' ? '已删除上传的用户头像' : 'Uploaded user avatar have been deleted';
                    } else if (typeStr === 'DeleteSettingsData') {
                        //'6) 删除[设置]数据', type: 'DeleteSettingsData'
                        //user头像恢复默认 删除可能存在的上传的用户头像数据
                        this.userAvatarUp_base64 = ''; //清空
                        this.userAvatarUp_blob = null; //清空
                        this.userAvatarUp_blobUrl = ''; //清空
                        this.inputUserImageUrl = '/img/me-bili.jpg'; //恢复默认头像url
                        this.updateAvatar('user', false);//刷新头像 false=Don't show notif.
                        //gpt头像恢复默认
                        this.inputGPTImageUrl = this.settingsLanguage == 'cn' ? this.GPTGreenSVG_UrlText_cn : this.GPTGreenSVG_UrlText_en;
                        this.updateAvatar('gpt', false);

                        //删除localStorage
                        localStorage.removeItem('userSettings');
                        //根据变量列表和默认值 还原默认变量
                        this.setVariablesToThis(this.userVariables_default, this.userVariables_default, true);

                        //还原默认变量后的操作 参考[恢复默认restoreDefault]中的步骤
                        this.openSmartMaxTokens = true; //默认开启，localStorage无此变量
                        this.changePageScaling(false);//需要在changeSimpleMode之前
                        this.changeLanguage();
                        this.changeSimpleMode(false);

                        this.inputapichange(false); // 重新验证API Key时 改变按钮等状态
                        this.btnDisabledState_SaveApiURL = true; //保存按钮状态改为不可用

                        this.changeStyle_FontSize();
                        this.changeStyle_Time_WechatStyle();
                        this.changeStyle_Time();
                        this.changeStyle_DividerLine();
                        this.changeContextualMode();


                        //无需保存

                        //通知
                        notif = this.settingsLanguage == 'cn' ? '已删除设置数据' : 'Settings data has been deleted';
                    }

                    setTimeout(() => {
                        this.showNotification(notif, 'bottom', '', notiftime);
                    }, delaytime);
                } catch (e) {
                    notif = this.settingsLanguage == 'cn' ? '出错了\r\n\r\n错误信息：' + e.message : 'Error\r\n\r\nError Message: ' + e.message;
                    this.showNotification(notif, 'center', 'Error - changeSelectedDeleteLocalStorageData()');
                    console.error('changeSelectedDeleteLocalStorageData() - 删除数据', e);
                }

            },

            // 设置窗口 - 数据管理Data Management 缓存localStorage 导出数据 Export JSON
            changeSelectedExportLocalStorageData() {
                if (this.checkBusyStatus('ForChat')) {
                    this.selectedExportLocalStorageData = ''; //复原
                    //底部会出现通知tooltips。 如果返回 true， 正在发送 或 正在验证 或 正在标签页间同步数据
                    return;
                }
                if (!this.isLocalStorageSupported) {
                    //浏览器当前不支持localStorage时
                    return;//已绑定禁用按钮
                }
                if (!this.isLocalStorageAllowed) {
                    //不允许缓存时
                    //临时禁止，可能存在历史数据，允许导出 return;
                }

                let notif = '';
                let typeStr = this.selectedExportLocalStorageData;
                this.selectedExportLocalStorageData = ''; //复原

                try {
                    //1 先保存一次  保存所有数据
                    this.localStorage_SaveData(null, null);

                    //2 第二次保存  可能会出现“同步”，加个延迟再保存一次
                    setTimeout(() => {
                        //2 第二次保存所有数据
                        this.localStorage_SaveData(null, null);
                        let dataString = '';
                        let fileName = this.exportJSONFileName;
                        let dataObject = {};

                        if (typeStr === 'ExportAllData') {
                            //'1) 一键导出所有数据
                            notif = this.settingsLanguage == 'cn' ? '正在导出JSON文件 - 所有数据' : 'Exporting JSON file - All Data';

                            //获取所有LinGPT相关的数据，目前有4个key
                            const includedKeys = ["userSettings", "chatDataList", "apikeyData", "promptsData"];
                            includedKeys.forEach(function (key) {
                                const value = localStorage.getItem(key);
                                if (value !== null && value !== undefined) {
                                    dataObject[key] = JSON.parse(value);
                                }
                            });

                            if (Object.keys(dataObject).length == 0) { //空
                                notif = this.settingsLanguage == 'cn' ? '浏览器本地存储(localStorage) - 数据为空，中止导出' : "No data available for export in the browser's local storage.";
                                this.showNotification(notif, 'bottom', '', 5500);
                                return;
                            }
                            fileName += 'AllData';//文件名

                        } else if (typeStr === 'ExportChatDataList') {
                            //'2) 导出[对话]数据
                            notif = this.settingsLanguage == 'cn' ? '正在导出JSON文件 - 所有对话数据' : 'Exporting JSON file - All Chat Data';
                            const data_chatDataList = localStorage.getItem('chatDataList');//获取数据
                            if (data_chatDataList == null) {
                                notif = this.settingsLanguage == 'cn' ? '浏览器本地存储(localStorage) - 无对话数据，中止导出' : "No Chat Data available for export in the browser's local storage.";
                                this.showNotification(notif, 'bottom', '', 5500);
                                return;
                            }
                            dataObject['chatDataList'] = JSON.parse(data_chatDataList);
                            fileName += 'AllChatData'; //文件名
                        } else if (typeStr === 'ExportAPIKeyData') {
                            //'3) 导出[密钥/API Key]数据
                            notif = this.settingsLanguage == 'cn' ? '正在导出JSON文件 - 密钥/API Key数据' : 'Exporting JSON file - API Keys Data';
                            const data_apikeyData = localStorage.getItem('apikeyData');//获取数据
                            const apikeydata_obj = JSON.parse(data_apikeyData);
                            if (data_apikeyData == null || apikeydata_obj == null || (apikeydata_obj.api === '' && (apikeydata_obj.apiCheckedData == null || apikeydata_obj.apiCheckedData.length === 0))) {
                                notif = this.settingsLanguage == 'cn' ? '浏览器本地存储(localStorage) - 无密钥/API Key数据，中止导出' : "No API Keys Data available for export in the browser's local storage.";
                                this.showNotification(notif, 'bottom', '', 5500);
                                return;
                            }
                            dataObject['apikeyData'] = JSON.parse(data_apikeyData);
                            fileName += 'APIKeyData'; //文件名
                        } else if (typeStr === 'ExportPromptsData') {
                            //'4) 导出[提示词]数据'
                            notif = this.settingsLanguage == 'cn' ? '正在导出JSON文件 - 提示词数据' : 'Exporting JSON file - Prompt Generator Data';
                            const data_promptsData = localStorage.getItem('promptsData');//获取数据
                            if (data_promptsData == null) {
                                notif = this.settingsLanguage == 'cn' ? '浏览器本地存储(localStorage) - 无提示词数据，中止导出' : "No Prompt Generator Data available for export in the browser's local storage.";
                                this.showNotification(notif, 'bottom', '', 5500);
                                return;
                            }
                            dataObject['promptsData'] = JSON.parse(data_promptsData);
                            fileName += 'PromptsData'; //文件名
                        } else if (typeStr === 'ExportSettingsData') {
                            //'5) 导出[设置]数据'
                            notif = this.settingsLanguage == 'cn' ? '正在导出JSON文件 - 设置数据' : 'Exporting JSON file - Settings Data';
                            const data_userSettings = localStorage.getItem('userSettings');//获取数据
                            if (data_userSettings == null) {
                                notif = this.settingsLanguage == 'cn' ? '浏览器本地存储(localStorage) - 无设置数据，中止导出' : "No Settings Data available for export in the browser's local storage.";
                                this.showNotification(notif, 'bottom', '', 5500);
                                return;
                            }
                            dataObject['userSettings'] = JSON.parse(data_userSettings);
                            fileName += 'SettingsData'; //文件名
                        }

                        this.showNotification(notif, 'bottom', '', 4000);
                        //转字符串
                        dataString = JSON.stringify(dataObject);
                        //导出
                        this.ExportData_JSON(dataString, fileName);
                    }, 150);//第二次保存，延迟保存
                } catch (e) {
                    notif = this.settingsLanguage == 'cn' ? '出错了\r\n\r\n错误信息：' + e.message : 'Error\r\n\r\nError Message: ' + e.message;
                    this.showNotification(notif, 'center', 'Error - changeSelectedExportLocalStorageData()');
                    console.error('changeSelectedExportLocalStorageData() - 导出数据', e);
                }

            },

            // 设置窗口 - 数据管理Data Management 缓存localStorage 导入数据 Import JSON
            importLocalStorageData() {
                if (this.checkBusyStatus('ForChat')) {
                    //底部会出现通知tooltips。 如果返回 true， 正在发送 或 正在验证 或 正在标签页间同步数据
                    return;
                }
                let notif = '';
                let notiftime = 2500; //通知显示的时间
                let delaytime = 0;//延迟时间
                const temp_settingsLanguage = this.settingsLanguage; //恢复设置可能会导致通知消息中出现两种语言，固定为恢复设置前的语言
                if (!this.isLocalStorageSupported) {
                    //浏览器当前不支持localStorage时
                    notif = this.settingsLanguage == 'cn' ? '当前浏览器或浏览器模式不支持本地存储(localStorage)功能' : 'The browser or browser mode does not support local storage.';
                }
                if (!this.isLocalStorageAllowed) {
                    //不允许缓存时
                    notif = this.settingsLanguage == 'cn' ? '当前浏览器本地存储已被禁用，导入功能不可用' : "The browser's local storage has been disabled and cannot be imported.";
                }
                if (notif !== '') {
                    this.showNotification(notif, 'bottom', '', 3500);
                    return;
                }
                notif = '';

                try {
                    //1 先保存一次  保存所有数据
                    this.localStorage_SaveData(null, null);

                    //2 第二次保存  可能会出现“同步”，加个延迟再保存一次
                    setTimeout(() => {
                        //2 第二次保存所有数据
                        this.localStorage_SaveData(null, null);

                        const isInputExit = document.getElementById('inputFileImportData');
                        const dialogSettings = document.getElementById("dialog-settings");

                        if (isInputExit) { //防止之前没删成功
                            isInputExit.onchange = null;
                            dialogSettings.removeChild(isInputExit);//移除
                        }

                        let fileInput = document.createElement('input');//新建导入控件
                        fileInput.type = 'file';
                        fileInput.accept = '.json';
                        fileInput.style.display = 'none';
                        fileInput.id = 'inputFileImportData';
                        //添加控件
                        dialogSettings.appendChild(fileInput);
                        //选取文件后 change事件
                        fileInput.onchange = (e) => {
                            //选取结果
                            let file = e.target.files[0];
                            //如果有选取文件
                            if (file) {
                                let reader = new FileReader();
                                //Start. 读取事件
                                reader.onload = (event) => {
                                    try {
                                        var contentsJSON = event.target.result;
                                        let dataObject = JSON.parse(contentsJSON);
                                        //错误 文件错误 格式错误
                                        if (dataObject === null || typeof dataObject === 'undefined' || typeof dataObject !== 'object') {
                                            notif = this.settingsLanguage == 'cn' ? '文件错误，非LinGPT的数据文件' : 'File error, not a data file for LinGPT';
                                            this.showNotification(notif, 'bottom', '', 6000);
                                            return;
                                        }
                                        //错误 无有效数据
                                        if (!dataObject.hasOwnProperty('chatDataList') && !dataObject.hasOwnProperty('apikeyData') && !dataObject.hasOwnProperty('promptsData') && !dataObject.hasOwnProperty('userSettings')) {
                                            notif = this.settingsLanguage == 'cn' ? '文件错误，未包含LinGPT数据' : 'File error, does not contain LinGPT data';
                                            this.showNotification(notif, 'bottom', '', 6000);
                                            return;
                                        }

                                        notif = temp_settingsLanguage == 'cn' ? '导入的数据清单：' : 'Imported data list:';

                                        //含有chatDataList数据 *数据会合并
                                        if (dataObject.hasOwnProperty('chatDataList')) {
                                            let newChatDataList = dataObject['chatDataList'];
                                            let newChatDataCount = 0;
                                            let duplicateChatDataCount = 0;
                                            let partiallyDuplicateChatDataCount = 0;
                                            let nonDuplicateChatDataCount = 0;

                                            newChatDataList.forEach(newChatData => {
                                                let isDuplicate = false;
                                                let renamedChatCreatedTime = newChatData['chatcreatedtime'];
                                                let renamedChatID = newChatData['chatid'];

                                                this.chatDataList.forEach(existingChatData => {
                                                    if (existingChatData['chatcreatedtime'] === newChatData['chatcreatedtime']) {
                                                        // Check if the chat data objects are the same
                                                        if (JSON.stringify(existingChatData) === JSON.stringify(newChatData)) {
                                                            isDuplicate = true;
                                                            duplicateChatDataCount++;
                                                            return;
                                                        }
                                                        // Rename the chatcreatedtime for the new chat data
                                                        renamedChatCreatedTime = this.newChatCreatedTime_ForCopyChatData(newChatData);//生成新的chatcreatedtime
                                                        renamedChatID += this.settingsLanguage == 'cn' ? ' 副本[导入冲突]' : ' Copy [Import Conflict]';
                                                        //chatlasttime 保持不变，不修改
                                                        partiallyDuplicateChatDataCount++;
                                                    }
                                                });

                                                if (!isDuplicate) {
                                                    newChatData['chatcreatedtime'] = renamedChatCreatedTime;
                                                    newChatData['chatid'] = renamedChatID;
                                                    this.chatDataList.push(newChatData);
                                                    nonDuplicateChatDataCount++;
                                                }

                                                newChatDataCount++;
                                            });
                                            nonDuplicateChatDataCount = nonDuplicateChatDataCount - partiallyDuplicateChatDataCount;//修正
                                            //console.log("New chat data count:", newChatDataCount);
                                            //console.log("Duplicate chat data count:", duplicateChatDataCount);
                                            //console.log("Partially duplicate chat data count:", partiallyDuplicateChatDataCount);
                                            //console.log("Non-duplicate chat data count:", nonDuplicateChatDataCount);

                                            //保存整个List
                                            if (this.isLocalStorageAllowed && this.isLocalStorageSupported) {
                                                localStorage.setItem('chatDataList', JSON.stringify(this.chatDataList));
                                            }
                                            const list_notif_cn = '\r\n\r\n  • 对话数据: OK，导入完成，数据已合并。JSON文件包含对话记录 ' + newChatDataCount + ' 条，其中：全新的对话 ' + nonDuplicateChatDataCount + ' 条；已存在的对话(完全一致) ' + duplicateChatDataCount + ' 条；内容冲突的对话(存在差异) ' + partiallyDuplicateChatDataCount + ' 条。';
                                            const list_notif_en = '\r\n\r\n  • Chat Data: OK, import successful, the data has been merged.  The JSON file contains ' + newChatDataCount + ' chat entries: ' + nonDuplicateChatDataCount + ' new entries, ' + duplicateChatDataCount + ' existing entries, and ' + partiallyDuplicateChatDataCount + ' conflicting entries.';
                                            notif += temp_settingsLanguage == 'cn' ? list_notif_cn : list_notif_en;
                                        }
                                        //含有apikeyData数据 *数据会合并
                                        if (dataObject.hasOwnProperty('apikeyData')) {
                                            let new_apikeyData = dataObject['apikeyData'];//只取其中的apiCheckedData数据
                                            //导入的已验证的apikey数组数据 apiCheckedData
                                            let new_apiCheckedData = new_apikeyData == null ? [] : new_apikeyData.apiCheckedData;
                                            new_apiCheckedData = new_apiCheckedData == null ? [] : new_apiCheckedData; //new_apikeyData.apiCheckedData可能为null（或被编辑过而产生错误）
                                            new_apiCheckedData = new_apiCheckedData.filter(apidata => apidata.apikey && apidata.apikey.trim() !== '');//排除空项，尤其是1.7.05版本的数据

                                            //delete if(new_apiCheckedData != null && new_apikeyData != null && new_apikeyData.version != null && new_apikeyData.version === '1.7.05' ){
                                            //delete 	//兼容性处理 升级1.7.05(本地存储的首个版本)的apiCheckedData数据   ~v7.13
                                            //delete 	new_apiCheckedData.forEach(apiData => {
                                            //delete 		Object.assign(apiData, {
                                            //delete 			date1: apiData.date1 || 0,
                                            //delete 			date2: apiData.date2 || 0,
                                            //delete 			date3: apiData.date3 || 0,
                                            //delete 			nexttime: apiData.nexttime || 0,
                                            //delete 			level: apiData.level || null,
                                            //delete 			freq: apiData.freq || 0,
                                            //delete 			vpn: apiData.vpn || '',
                                            //delete 			owner: apiData.owner || 'me',
                                            //delete 			succcount: apiData.succcount || 0,
                                            //delete 			failcount: apiData.failcount || 0,
                                            //delete 			succrate: apiData.succrate || 0,
                                            //delete 			updatetime: apiData.updatetime || 0
                                            //delete 		});
                                            //delete 	});
                                            //delete }

                                            // 循环new_apiCheckedData 验证数据 & 兼容性处理
                                            //判断属性是否存在且数据是否合法，如果缺少属性 或 属性数据不合法，就补上属性和默认值
                                            this.validateApiCheckedData(new_apiCheckedData);


                                            // 合并更新数据到this.apiCheckedData
                                            const newData = this.apiCheckedData.filter(apidata => apidata.apikey && apidata.apikey.trim() !== '');//排除空项

                                            //数据统计
                                            let updatedApiCount = 0;//存在-较新数据-覆盖
                                            let notUpdatedApiCount = 0;//存在-较旧数据-不覆盖
                                            let newApiCount = 0;//不存在-新增
                                            let existingApiCount = 0;//已存在

                                            //更新差异数据， 新增 或 更新
                                            new_apiCheckedData.forEach(apidata => {
                                                //为了保证成功率的准确度和实效性，合并时需要修改成功次数 与 失败次数
                                                //*如果数字很大，导入后成功率很难变化。所以合并时限制成功次数/失败次数最高为20次，导入后，成功率会根据实际的使用情况快速变化
                                                //当 apidata.succcount 或 apidata.failcount 大于 20 时，我们需要将它们的值按比例缩小到 20 以内
                                                if (apidata.succcount > 20 || apidata.failcount > 20) {
                                                    const maxCount = Math.max(apidata.succcount, apidata.failcount);
                                                    const scaleFactor = maxCount / 20;
                                                    apidata.succcount = Math.ceil(apidata.succcount / scaleFactor);
                                                    apidata.failcount = Math.ceil(apidata.failcount / scaleFactor);
                                                }


                                                const existingApiIndex = newData.findIndex(api => api.apikey === apidata.apikey);
                                                if (existingApiIndex !== -1) {//存在 - 更新
                                                    const existingApi = newData[existingApiIndex];
                                                    // 比较updatetime，以更新最新的数据
                                                    if (apidata.updatetime > existingApi.updatetime) {
                                                        newData[existingApiIndex] = apidata;
                                                        updatedApiCount++;//覆盖 统计
                                                    } else {
                                                        notUpdatedApiCount++;//不覆盖 统计
                                                    }
                                                } else {//不存在 - 新增
                                                    newData.push(apidata);
                                                    newApiCount++;//新增 统计
                                                }
                                            });
                                            this.apiCheckedData = newData;
                                            existingApiCount = (updatedApiCount + notUpdatedApiCount);

                                            //console.log("导入的数量: " + new_apiCheckedData.length);
                                            //console.log("已存在的数量: " + existingApiCount);
                                            //console.log("已存在 - 覆盖更新的数量: " + updatedApiCount);
                                            //console.log("已存在 - 未覆盖的数量: " + notUpdatedApiCount);
                                            //console.log("新增的数量: " + newApiCount);

                                            const apikeyData = this.getVariablesFromThis(this.apikeyData_default);
                                            //手动增加版本
                                            apikeyData.version = this.apikeyData_Version;
                                            //先保存一次
                                            localStorage.setItem('apikeyData', JSON.stringify(apikeyData));

                                            if (!this.btnDisabledState_CheckAPI) { //如果当前是“未通过验证”的状态
                                                //恢复apikey到输入框，并恢复按钮状态
                                                this.restoreApiKeyInput(false, false);
                                            }

                                            const apikey_notif_cn = '\r\n\r\n  • 密钥/API Key数据: OK，导入完成，数据已合并，将自动轮换API Key。JSON文件包含API Key数量 ' + new_apiCheckedData.length + ' 个，其中：新的 ' + newApiCount + ' 个；已存在的 ' + existingApiCount + ' 个 (更新较新使用日期的 ' + updatedApiCount + ' 个，无需更新的 ' + notUpdatedApiCount + ' 个)。';
                                            const apikey_notif_en = '\r\n\r\n  • API Keys Data: OK, import successful, the data has been merged. Additionally, the API Keys will be automatically rotated.  The JSON file contains ' + new_apiCheckedData.length + ' API Keys: ' + newApiCount + ' new keys; ' + existingApiCount + ' existing keys (updated ' + updatedApiCount + ' data with newer usage dates and ' + notUpdatedApiCount + ' do not require updates).';
                                            notif += temp_settingsLanguage == 'cn' ? apikey_notif_cn : apikey_notif_en;

                                        }
                                        //含有promptsData数据 *数据会合并
                                        if (dataObject.hasOwnProperty('promptsData')) {
                                            notif += temp_settingsLanguage == 'cn' ? '\r\n\r\n  • 提示词数据: 暂不支持导入' : '\r\n\r\n  • Prompt Generator Data: Import is not supported at the moment, under development.';
                                            //1111111111
                                        }
                                        //含有userSettings数据 *数据不会合并，只会覆盖
                                        if (dataObject.hasOwnProperty('userSettings')) {
                                            // 还原用户设置数据 userSettings
                                            this.islocalStorageFirstLoad = true;//open  *RestoreData_userSettings中有此开关
                                            this.RestoreData_userSettings(dataObject['userSettings']);//还原
                                            this.islocalStorageFirstLoad = false;//close

                                            notif += temp_settingsLanguage == 'cn' ? '\r\n\r\n  • 设置数据: OK，导入完成，已更新为新的设置' : '\r\n\r\n  • Settings Data: OK, import successful, the data has been replaced with new settings.';
                                        }

                                        //保存 刷新设置参数
                                        setTimeout(() => {
                                            //保存 保存所有数据
                                            this.localStorage_SaveData(null, null);
                                            //涉及到设置参数的变化，重新读取一次，会应用设置参数
                                            const chat_Data = this.chatDataList.find(c => c.chatcreatedtime === this.chatcreatedtime);
                                            this.loadChatData(chat_Data);//读取

                                            this.changePageScaling(false);//需要在changeSimpleMode之前
                                            this.changeLanguage();
                                            this.changeSimpleMode(false);
                                            //其他读取后需要处理的
                                            this.$nextTick(() => {
                                                // 1 2 3 4 5
                                                this.todoList_ChatDataRestored('hasdata');
                                            });
                                        }, 0);
                                        //通知
                                        const title = temp_settingsLanguage == 'cn' ? '导入报告' : 'Import Report';
                                        setTimeout(() => {
                                            this.showNotification(notif, 'center', title);
                                        }, 650);
                                    } catch (e) { //reader.onload = (event) =>
                                        const e_message = e.message ? e.message : '';
                                        const e_name = e.name ? e.name : '';
                                        if (!this.localStorageExceedsMaximum(e_message, e_name)) { //先判断是否本地存储满了 Determining if it is due to running out of local storage capacity
                                            notif = this.settingsLanguage == 'cn' ? '出错了\r\n\r\n错误信息：' + e.message : 'Error\r\n\r\nError Message: ' + e.message;
                                            this.showNotification(notif, 'center', 'Err - { reader.onload = (event) => }');
                                            console.error('importLocalStorageData() - reader.onload = (event) => 事件', e);
                                        } else {
                                            //提醒用户 存储空间用量 及 文件大小
                                            let localStorageSize_A = null; //所有本地数据 算法A
                                            let localStorageSize_B = null; //所有本地数据 算法B
                                            localStorageSize_A = this.calculateLocalStorageSize();
                                            localStorageSize_B = this.calculateLocalStorageUsage();
                                            notif = this.settingsLanguage == 'cn' ? '导入失败 - 本地存储空间不足\r\n\r\n' : 'Import Failed - Insufficient Local Storage Space\r\n\r\n';
                                            let notif_temp = '';

                                            if (localStorageSize_A !== null && localStorageSize_B !== null && localStorageSize_A !== localStorageSize_B) {
                                                // 均不为null
                                                //判断大小，小的赋值给A，大的赋值给B
                                                if (localStorageSize_A > localStorageSize_B) {
                                                    let temp = localStorageSize_A;
                                                    localStorageSize_A = localStorageSize_B;
                                                    localStorageSize_B = temp;
                                                }
                                                notif_temp = this.settingsLanguage == 'cn' ? '当前浏览器本地存储已使用 ' : "The current browser's local storage usage is ";
                                                notif_temp = notif_temp + (this.settingsLanguage == 'cn' ? localStorageSize_A + ' ～ ' + localStorageSize_B + ' MB' : localStorageSize_A + ' ～ ' + localStorageSize_B + ' MB');
                                                notif_temp = '\r\n\r\n' + notif_temp + '。';
                                            } else {
                                                // 只有一个变量为 null 的情况 , 这里不考虑都等于null的情况
                                                localStorageSize_A = localStorageSize_A === null ? localStorageSize_B : localStorageSize_A;
                                                notif_temp = this.settingsLanguage == 'cn' ? '当前浏览器本地存储已使用 ' : "The current browser's local storage usage is ";
                                                notif_temp = notif_temp + (this.settingsLanguage == 'cn' ? localStorageSize_A + ' MB' : localStorageSize_A + ' MB');
                                                notif_temp = '\r\n\r\n' + notif_temp + '。';
                                            }

                                            const sizeInMB = this.calculateStringSize(contentsJSON, 'utf-16') / 1024 / 1024;
                                            const sizeStr = sizeInMB.toFixed(3) + ' MB';
                                            notif = notif + (this.settingsLanguage == 'cn' ? '本次导入的 JSON 数据大小为 ' + sizeStr : 'The size of the JSON data imported this time is ' + sizeStr);
                                            notif = notif + notif_temp + (this.settingsLanguage == 'cn' ? '浏览器通常仅支持约 5MB 或 10MB 的本地存储空间 (localStorage)，不同浏览器和版本之间可能会有所差异。如果由于浏览器本地存储空间不足而导入失败，您可以尝试删除一些对话数据 (或先抹掉日志)，或者尝试更换浏览器后再次导入。\r\n建议: 在重新导入之前先刷新页面。' : 'Browsers typically only support around 5MB or 10MB of local storage space, with variations across different browsers and versions. If the import fails due to insufficient browser storage space, you can try deleting some chat data or erase logs, or switch to a different browser and try again. \r\nSuggestion: Refresh the page before importing again.');
                                            alert(notif);
                                        }
                                    }
                                    //End. reader.onload = (event) => ...
                                };
                                //读取内容
                                reader.readAsText(file);
                            }
                            // 从DOM中移除fileInput元素
                            fileInput.remove();
                        }//End - fileInput.onchange()

                        notif = this.settingsLanguage == 'cn' ? '导入数据：自动验证由LinGPT导出的JSON数据文件' : 'Import: Auto-validating the JSON data file exported by LinGPT.';
                        this.showNotification(notif, 'bottom', '', 20000);

                        //激活导入控件，弹出文件选取框
                        fileInput.click();

                    }, 150);//第二次保存，延迟保存
                } catch (e) {
                    const e_message = e.message ? e.message : '';
                    const e_name = e.name ? e.name : '';
                    if (!this.localStorageExceedsMaximum(e_message, e_name)) { //先判断是否本地存储满了 Determining if it is due to running out of local storage capacity
                        notif = this.settingsLanguage == 'cn' ? '出错了\r\n\r\n错误信息：' + e.message : 'Error\r\n\r\nError Message: ' + e.message;
                        this.showNotification(notif, 'center', 'Err - importLocalStorageData()');
                        console.error('importLocalStorageData() - 导入数据', e);
                    }
                }

            },

            // 设置窗口 - 数据管理Data Management 缓存localStorage 计算
            getLocalStorageSize() {
                if (!this.isLocalStorageSupported) {
                    return;//浏览器当前不支持localStorage时
                }
                if (!this.isLocalStorageAllowed) {
                    // return; 不允许缓存时，不代表没有数据
                }
                if (this.checkBusyStatus('ForChat')) {
                    //底部会出现通知tooltips。 如果返回 true， 正在发送 或 正在验证 或 正在标签页间同步数据
                    return;
                }

                //1 先保存一次  保存当前数据
                this.localStorage_SaveData(null, 'chatDataList');//保存对话

                //2 第二次保存  保存所有数据,包括上传的头像。 两次保存之间可能会出现“同步”，加个延迟
                setTimeout(() => {
                    this.localStorage_SaveData(null, null);//保存所有数据

                    //保存后 延迟计算
                    setTimeout(() => {
                        let localStorageSize_A = null; //所有本地数据 算法A
                        let localStorageSize_B = null; //所有本地数据 算法B
                        let avatarSize = null; //单独-上传头像
                        let chatDataListSize = null; //单独-聊天数据
                        let promptsDataSize = null; //单独-提示词
                        let chatDataListSize_chatHistoryLogs = 0; //单独-聊天数据中的 Logs部分
                        try {
                            localStorageSize_A = this.calculateLocalStorageSize();
                        } catch (e) {
                        }
                        try {
                            localStorageSize_B = this.calculateLocalStorageUsage();
                        } catch (e) {
                        }
                        try {
                            avatarSize = this.calculateLocalStorageUsage_Item('avatar');
                        } catch (e) {
                        }
                        try {
                            chatDataListSize = this.calculateLocalStorageUsage_Item('chatDataList');
                        } catch (e) {
                        }
                        try {
                            promptsDataSize = this.calculateLocalStorageUsage_Item('promptsData');
                        } catch (e) {
                        }
                        try { //v7.27 计算日志 (用于导出单个对话的markdown格式的全部聊天记录与操作记录)
                            let logs_totalSize = 0;
                            this.chatDataList.forEach((chatData) => {
                                const chathistoryString = chatData.chatVariables.chathistory;
                                const sizeInMB = this.calculateStringSize(chathistoryString, 'utf-16') / 1024 / 1024;
                                logs_totalSize += sizeInMB;
                            });
                            chatDataListSize_chatHistoryLogs = logs_totalSize.toFixed(4);
                        } catch (e) {
                        }

                        let notif = '';
                        const head = this.settingsLanguage == 'cn' ? '浏览器本地存储 (localStorage) 使用情况：\r\n' : 'The usage of browser local storage (localStorage) :\r\n';

                        avatarSize = avatarSize == null ? 0 : avatarSize;
                        chatDataListSize = chatDataListSize == null ? 0 : chatDataListSize;
                        promptsDataSize = promptsDataSize == null ? 0 : promptsDataSize;
                        avatarSize = avatarSize.toString() === '0' ? '0.0000' : avatarSize;
                        chatDataListSize = chatDataListSize.toString() === '0' ? '0.0000' : chatDataListSize;
                        promptsDataSize = promptsDataSize.toString() === '0.0003' ? ' < 0.0003' : promptsDataSize;
                        chatDataListSize_chatHistoryLogs = chatDataListSize_chatHistoryLogs === null ? '0.0000' : chatDataListSize_chatHistoryLogs;
                        let otherSizeStr = '\r\n';
                        otherSizeStr += this.settingsLanguage == 'cn' ? '\r\n  部分数据的存储情况如下:' : '\r\n  Storage status for some data:';
                        otherSizeStr += this.settingsLanguage == 'cn' ? '\r\n  • 聊天数据占用：' + chatDataListSize + ' MB  (其中日志部分占用：' + chatDataListSize_chatHistoryLogs + ' MB)' : '\r\n  • Chat data size: ' + chatDataListSize + ' MB  (with "Logs" size: ' + chatDataListSize_chatHistoryLogs + ' MB)';
                        otherSizeStr += this.settingsLanguage == 'cn' ? '\r\n  • 用户头像(转码后)占用：' + avatarSize + ' MB' : '\r\n  • User avatar (encoded) size: ' + avatarSize + ' MB';
                        otherSizeStr += this.settingsLanguage == 'cn' ? '\r\n  • 提示词数据占用：' + promptsDataSize + ' MB' : '\r\n  • Prompts data size: ' + promptsDataSize + ' MB';
                        otherSizeStr += '\r\n  • ...';

                        //if ( !this.isSimpleMode ){
                        otherSizeStr += this.settingsLanguage == 'cn' ? '\r\n\r\n  小贴士:' : '\r\n\r\n  Tips:';
                        otherSizeStr += this.settingsLanguage == 'cn' ? '\r\n  • 电脑端浏览器本地存储通常为 10MB' : '\r\n  • Local storage for desktop browsers is typically 10MB.';
                        otherSizeStr += this.settingsLanguage == 'cn' ? '\r\n  • 移动端浏览器本地存储通常为 5MB' : '\r\n  • Local storage for mobile browsers is typically 5MB.';
                        otherSizeStr += this.settingsLanguage == 'cn' ? '\r\n  • Safari (iPhone / Mac) 的本地存储一般为 5MB' : '\r\n  • Local storage for Safari (iPhone / Mac) is usually 5MB.';
                        otherSizeStr += this.settingsLanguage == 'cn' ? '\r\n  • "日志": 单个对话的所有记录 (包含操作记录、撤回的内容以及发送失败的内容等)，用于导出 Markdown 格式的纯文本聊天记录，有些对话日志可能会比较大。' : '\r\n  • "Logs": All records of individual conversations, including activity logs, retracted messages, and failed messages, are stored. These logs can be exported as plain text chat records in Markdown format. Some logs of chats may be large in size.';
                        otherSizeStr += this.settingsLanguage == 'cn' ? '\r\n  • 如何删除对话日志: 打开 [对话列表]，点击 [...] 选 抹掉日志' : '\r\n  • How to delete chat logs: Open the [Chat List], click on [...], and select Erase logs.';
                        //}

                        if (localStorageSize_A !== null && localStorageSize_B !== null && localStorageSize_A !== localStorageSize_B) {
                            // 均不为null
                            //判断大小，小的赋值给A，大的赋值给B
                            if (localStorageSize_A > localStorageSize_B) {
                                let temp = localStorageSize_A;
                                localStorageSize_A = localStorageSize_B;
                                localStorageSize_B = temp;
                            }
                            notif = this.settingsLanguage == 'cn' ? '已使用：' + localStorageSize_A + ' ～ ' + localStorageSize_B + ' MB' : 'Used: ' + localStorageSize_A + ' ～ ' + localStorageSize_B + ' MB';
                            notif = head + notif;
                            if ((5 - localStorageSize_B) > -0.1) {
                                notif += this.settingsLanguage == 'cn' ? '\r\n预计剩余可用 (假设上限为 5MB)：' + (5 - localStorageSize_B).toFixed(4) + ' MB\r\n预计剩余可用 (假设上限为 10MB)：' + (10 - localStorageSize_B).toFixed(4) + ' MB' : '\r\nEstimated Remaining Size (assuming a limit of 5MB): ' + (5 - localStorageSize_B).toFixed(4) + ' MB\r\nEstimated Remaining Size (assuming a limit of 10MB): ' + (10 - localStorageSize_B).toFixed(4) + ' MB';
                            } else {
                                notif += this.settingsLanguage == 'cn' ? '\r\n预计剩余可用 (假设上限为 10MB)：' + (10 - localStorageSize_B).toFixed(4) + ' MB' : '\r\nEstimated Remaining Size (assuming a limit of 10MB): ' + (10 - localStorageSize_B).toFixed(4) + ' MB';
                            }
                            notif += otherSizeStr;
                        } else if ((localStorageSize_A === null && localStorageSize_B !== null) || (localStorageSize_A !== null && localStorageSize_B === null) || (localStorageSize_A !== null && localStorageSize_A === localStorageSize_B)) {
                            // 只有一个变量为 null 的情况
                            localStorageSize_A = localStorageSize_A === null ? localStorageSize_B : localStorageSize_A;
                            notif = this.settingsLanguage == 'cn' ? '已使用：' + localStorageSize_A + ' MB' : 'Used: ' + localStorageSize_A + ' MB';
                            notif = head + notif;
                            if ((5 - localStorageSize_B) > -0.1) {
                                notif += this.settingsLanguage == 'cn' ? '\r\n预计剩余可用 (假设上限为 5MB)：' + (5 - localStorageSize_A).toFixed(4) + ' MB\r\n预计剩余可用 (假设上限为 10MB)：' + (10 - localStorageSize_A).toFixed(4) + ' MB' : '\r\nEstimated Remaining Size (assuming a limit of 5MB): ' + (5 - localStorageSize_A).toFixed(4) + ' MB\r\nEstimated Remaining Size (assuming a limit of 10MB): ' + (10 - localStorageSize_A).toFixed(4) + ' MB';
                            } else {
                                notif += this.settingsLanguage == 'cn' ? '\r\n预计剩余可用 (假设上限为 10MB)：' + (10 - localStorageSize_A).toFixed(4) + ' MB' : '\r\nEstimated Remaining Size (assuming a limit of 10MB): ' + (10 - localStorageSize_A).toFixed(4) + ' MB';
                            }
                            notif += otherSizeStr;
                        } else {
                            // 均为null
                            notif = this.settingsLanguage == 'cn' ? '出错了，计算失败。' : 'Error occurred, calculation failed.';
                            notif = head + notif;
                        }

                        let notiftime = 0;
                        if (this.isShowNotification) {
                            notiftime = 1300; //如果通知弹窗已经存在，延迟1.3秒弹出
                        }
                        setTimeout(() => {
                            const title = this.settingsLanguage == 'cn' ? '用量统计' : 'Usage statistics';
                            this.showNotification(notif, 'center', title);
                        }, notiftime);//延迟弹出

                    }, 100);//延迟计算
                }, 150);//第二次保存，延迟保存

            },
            //计算本地存储的大小 方法A
            calculateLocalStorageSize() {
                let totalSize = 0;

                // 遍历localStorage的每个项目
                for (var i = 0; i < localStorage.length; i++) {
                    let key = localStorage.key(i);
                    let value = localStorage.getItem(key);

                    // 将键和值拼接成字符串，计算大小
                    let itemSize = this.calculateStringSize(key + value, 'utf-16');
                    totalSize += itemSize;
                }

                totalSize = totalSize / (1024 * 1024); //convertToMB
                totalSize = totalSize.toFixed(4);

                return totalSize;
            },
            //计算本地存储的大小 方法A 算法
            //calculateStringSize()代码来源于网络，其他的代码由GPT完成
            calculateStringSize(str, charset) {
                var total = 0,
                    charCode,
                    i,
                    len;
                charset = charset ? charset.toLowerCase() : '';
                if (charset === 'utf-16' || charset === 'utf16') {
                    for (i = 0, len = str.length; i < len; i++) {
                        charCode = str.charCodeAt(i);
                        if (charCode <= 0xffff) {
                            total += 2;
                        } else {
                            total += 4;
                        }
                    }
                } else {
                    for (i = 0, len = str.length; i < len; i++) {
                        charCode = str.charCodeAt(i);
                        if (charCode <= 0x007f) {
                            total += 1;
                        } else if (charCode <= 0x07ff) {
                            total += 2;
                        } else if (charCode <= 0xffff) {
                            total += 3;
                        } else {
                            total += 4;
                        }
                    }
                }
                return total;
            },
            //计算本地存储的大小 方法B *在本地存储中，数据通常以字符串的形式存储。在大多数情况下，字符串是以UTF-16编码表示的，这意味着每个字符占用2个字节（16位）
            calculateLocalStorageUsage() {
                let totalBytes = 0;

                // 遍历localStorage的每个项目
                for (var i = 0; i < localStorage.length; i++) {
                    let key = localStorage.key(i);
                    let value = localStorage.getItem(key);

                    // 计算每个项目的字节数
                    let bytes = key.length + value.length * 2;
                    totalBytes += bytes;
                }

                // 将字节数转换为可读的容量格式
                let bytesToKB = totalBytes / 1024;
                let bytesToMB = bytesToKB / 1024;
                bytesToMB = bytesToMB.toFixed(4);

                // 返回总容量
                return bytesToMB;
            },
            //计算本地存储-单个Item的大小  固定3项：1) avatar查头像占用大小   2) chatDataList查聊天记录占用大小   3) promptsData查提示词占用大小
            calculateLocalStorageUsage_Item(keyname) {
                keyname = keyname === 'avatar' ? 'userSettings' : keyname; //头像存储在userSettings中
                let objectString = localStorage.getItem(keyname);
                let bytesToMB = 0;
                if (objectString) {
                    if (keyname === 'userSettings') {
                        let userSettings = JSON.parse(objectString);
                        if (userSettings.avatar) {//转码后的头像
                            // 字节数需要x2  转KB  转MB  留4位小数点
                            bytesToMB = (JSON.stringify(userSettings.avatar).length * 2 / 1024 / 1024).toFixed(4);
                            // 返回容量
                            return bytesToMB;
                        }
                    } else if (keyname === 'chatDataList' || keyname === 'promptsData') {
                        // 字节数需要x2  转KB  转MB  留4位小数点
                        bytesToMB = ((keyname.length + objectString.length * 2) / 1024 / 1024).toFixed(4);
                        // 返回容量
                        return bytesToMB;
                    }
                }
                return null;
            },
            //本地存储超出浏览器的最大限制
            localStorageExceedsMaximum(errormessage, errorname) {
                errormessage = errormessage == null ? '' : errormessage;
                errorname = errormessage == null ? '' : errorname;
                const errName = errorname.toUpperCase();
                const temp_a = errormessage.includes("quota") && errormessage.includes("exceeded");
                const temp_b = errorname.includes("QuotaExceededError") || (errName.includes("EXCEEDED") && errName.includes("QUOTA")) || (errName.includes("ERROR") && errName.includes("QUOTA"));
                if (temp_a || temp_b) {
                    let notif_cn = "  • 您的浏览器本地存储已超过最大容量限制。\r\n  • 如果频繁弹出本提示，是因为自动保存时由于存储空间不足而保存失败，请尽快释放存储空间，否则新数据将持续保存失败 (最近的数据会丢失)。\r\n\r\n小贴士:\r\n  • 不同浏览器的本地存储限额不同，常见的限额为 10MB或 5MB。您可以打开新页面，[设置] > [数据管理与隐私]中，点击打开最后一个 [用量统计]，以查看本地存储的使用情况。";
                    notif_cn += "\r\n  • 在  [数据管理与隐私] 中您可以删除全部对话数据，在 [对话列表窗口] 中您可以选择性删除单个对话数据或抹掉对话日志。需要备份的对话可选择导出 JSON 文件。\r\n  • 导入时请注意: 1) 确保本地存储有足够的可用空间来容纳导入的数据; 2) 超过 5MB 的数据需要使用支持 10MB 本地存储的浏览器来进行导入; 3) 可使用隐私模式或使用其他浏览器来临时处理数据。";
                    let notif_en = "  • The browser's local storage has exceeded the maximum capacity limit.\r\n  • If this prompt frequently pops up, it is because the automatic saving fails due to insufficient storage space. Please free up storage space as soon as possible, otherwise new data will continue to fail to save (The most recent data will be lost).\r\n\r\nTips:\r\n  • Different browsers have different limits for local storage, with the most common limits being 10MB or 5MB. You can open a new page and go to [Settings] > [Data Privacy and Management]. Click on the last option [Usage Statistics] to view the usage of local storage.";
                    notif_en += "\r\n  • In [Data Privacy and Management], you can delete all chat data, and in [Chat List], you can selectively delete individual chat data or erase logs. Before deletion, you can choose to export a JSON file as a backup.\r\n  • Note when importing: 1) Ensure sufficient available space in local storage to accommodate imported data; 2) Use a browser that supports 10MB local storage for importing data exceeding 5MB; 3) Use privacy mode or an alternative browser to temporarily handle the data.";
                    const notif = this.settingsLanguage == 'cn' ? notif_cn : notif_en;
                    const title = this.settingsLanguage == 'cn' ? '本地存储 (localStorage) 空间不足' : "Insufficient local storage space";
                    setTimeout(() => {
                        this.showNotification(notif, 'center', title);
                    }, 2000);//延迟，避免之前有通知
                    return true;
                } else {
                    return false;
                }
            },


            //事件 数据同步 数据差异 数据合并
            localStorageEvent_chatDataList(key, newValue, oldValue) {
                if (!this.isLocalStorageAllowed || !this.isLocalStorageSupported) {
                    return; //不支持缓存或不允许缓存，返回
                }

                this.localStorageEvent_chatDataList_update_start = true; //更新开始

                const newData = JSON.parse(event.newValue || '[]'); // 将新数据转换为数组
                const oldData = JSON.parse(event.oldValue || '[]'); // 将旧数据转换为数组
                const added = newData.filter(x => !oldData.some(y => y.chatcreatedtime === x.chatcreatedtime)); // 提取新增的数据
                const removed = oldData.filter(x => !newData.some(y => y.chatcreatedtime === x.chatcreatedtime)); // 提取删除的数据
                const modified = newData.filter(x => {
                    const oldItem = oldData.find(y => y.chatcreatedtime === x.chatcreatedtime);
                    return oldItem && JSON.stringify(oldItem) !== JSON.stringify(x);
                }); // 提取修改的数据
                //console.log(new Date().toLocaleTimeString(), ' 新增的数据：', added);
                //console.log(new Date().toLocaleTimeString(), ' 删除的数据：', removed);
                //console.log(new Date().toLocaleTimeString(), ' 修改的数据：', modified);

                added.forEach(chatdata => {
                    if (chatdata.chatcreatedtime === null) {
                        // 处理新增的数据，id 为 null
                        // 无需处理
                    } else if (chatdata.chatcreatedtime === this.chatcreatedtime) {
                        // 处理新增的数据，id 等于 selectID
                        // 警告其他窗口新增相同id的数据 意外 已自动创建副本  原id同步
                        // 正常不应该出现这种情况，同步代码逻辑不够完善才有可能出现此情况

                        //1) 创建副本
                        let chat_Data = this.getSelectedChatData();//获取当前chatdata
                        chat_Data.chatcreatedtime = this.newChatCreatedTime_ForCopyChatData(chat_Data);//生成新的chatcreatedtime
                        chat_Data.chatlasttime = this.formatDateYYYYMMDDHHmmss(new Date(), ':'); //现在的时间
                        chat_Data.chatid += this.settingsLanguage == 'cn' ? ' 副本[同步-创建时间冲突]' : ' Copy [Sync - Created Time Conflict]';
                        const chatDataCopy = JSON.parse(JSON.stringify(chat_Data));

                        //新增到List
                        this.chatDataList.unshift(chat_Data);

                        //读取 读取对话数据 还原聊天记录 恢复聊天设置
                        this.loadChatData(chat_Data);

                        //其他读取后需要处理的
                        this.$nextTick(() => {
                            // 1 2 3 4 5
                            this.todoList_ChatDataRestored('hasdata');
                        });

                        //2) 同步新增原始chatdata
                        this.chatDataList.unshift(chatdata);
                        this.chatDataList_Sort();// ChatDataList排序

                        //3) 保存： 副本
                        this.saveChatData_Single(chat_Data); //单一的保存，不影响界面任何数据

                        //回到顶部
                        this.gotoTop_List();
                    } else {
                        // 处理新增的数据，id 不等于 selectID
                        // 新增
                        this.chatDataList.unshift(chatdata);
                        this.chatDataList_Sort();// ChatDataList排序
                    }
                })

                removed.forEach(chatdata => {
                    if (chatdata.chatcreatedtime === null) {
                        // 处理删除的数据，id 为 null
                        // 无需处理
                    } else if (chatdata.chatcreatedtime === this.chatcreatedtime) {
                        // 处理删除的数据，id 等于 selectID
                        // 警告其他窗口删除了当前对话，请问是否保留副本？  继续：1)创建副本+2)同步删除。   取消：1)同步删除。

                        // 6.26 改为不警告，直接删除

                        const del_chatid = this.chatid;

                        //1) 同步删除原始chatdata
                        this.chatDataList = this.chatDataList.filter(c => c.chatcreatedtime !== chatdata.chatcreatedtime); //删除目标对话，保留其他对话

                        //2)
                        if (this.chatDataList == [] || this.chatDataList.length == 0) {
                            //延迟弹消息
                            setTimeout(() => {
                                const notif = this.settingsLanguage == 'cn' ? '当前对话 { #' + del_chatid + ' } 在同步过程中被删除。\r\n当前列表空，已为您自动添加新的对话' : 'The chat { #' + del_chatid + ' } was deleted during synchronization.\r\nThe chat list is empty. A new chat has been automatically added.';
                                const title = this.settingsLanguage == 'cn' ? '浏览器本地存储 - 同步 - 通知' : 'Browser Local Storage Sync Notification';
                                this.showNotification(notif, 'center', title);
                            }, 1400);
                            //添加新对话
                            this.chatList_addNewChatData(true);
                        } else {
                            //延迟弹消息
                            setTimeout(() => {
                                const notif = this.settingsLanguage == 'cn' ? '当前对话 { #' + del_chatid + ' } 在同步过程中被删除' : 'The chat { #' + del_chatid + ' } was deleted during synchronization.';
                                const title = this.settingsLanguage == 'cn' ? '浏览器本地存储 - 同步 - 通知' : 'Browser Local Storage Sync Notification';
                                this.showNotification(notif, 'center', title);
                            }, 1400);

                            //7 读取 读取对话数据 还原聊天记录 恢复聊天设置
                            this.loadChatData(this.chatDataList[0]);

                            //其他读取后需要处理的
                            this.$nextTick(() => {
                                // 1 2 3 4 5 6
                                this.todoList_ChatDataRestored('hasdata', 50);
                            });

                            //回到顶部
                            this.gotoTop_List();
                        }
                        //}

                    } else {
                        // 处理删除的数据，id 不等于 selectID
                        // 直接删除
                        this.chatDataList = this.chatDataList.filter(c => c.chatcreatedtime !== chatdata.chatcreatedtime); //删除目标对话，保留其他对话
                    }
                })

                modified.forEach(chatdata => {
                    if (chatdata.chatcreatedtime === null) {
                        // 处理修改的数据，id 为 null
                        // 无需处理
                    } else if (chatdata.chatcreatedtime === this.chatcreatedtime) {
                        // 处理修改的数据，id 等于 selectID
                        // 警告其他窗口修改 是否保留副本 继续则创建副本+同步修改 取消则同步修改

                        // 6.26 改为不警告，直接增加副本 + 删除

                        //获取当前chatdata（最新）
                        let chat_Data = this.getSelectedChatData();
                        //对比chatdata，差异是否超出 "chatlasttime"、"chatid"、"chattitle"、"version"、"msg" 这4项。 超出true=直接覆盖 不创建副本。 未超出false=不覆盖 创建副本。
                        //即 只是修改id 和 title，不修改创建副本
                        const need_to_create_copy = !this.compareChatData(chatdata, chat_Data);

                        if (need_to_create_copy) {
                            const old_chatid = this.chatid;

                            // 1) 创建副本
                            //let chat_Data = this.getSelectedChatData();//获取当前chatdata
                            chat_Data.chatcreatedtime = this.newChatCreatedTime_ForCopyChatData(chat_Data);//生成新的chatcreatedtime
                            chat_Data.chatlasttime = this.formatDateYYYYMMDDHHmmss(new Date(), ':'); //现在的时间
                            chat_Data.chatid += this.settingsLanguage == 'cn' ? ' 副本[同步冲突]' : ' Copy [Sync Conflict]';
                            const chatDataCopy = JSON.parse(JSON.stringify(chat_Data));

                            const new_chatid = chat_Data.chatid;

                            //新增到List
                            this.chatDataList.unshift(chatDataCopy);

                            //读取 读取对话数据 还原聊天记录 恢复聊天设置
                            this.loadChatData(chatDataCopy);

                            //其他读取后需要处理的
                            this.$nextTick(() => {
                                // 1 2 3 4 5
                                this.todoList_ChatDataRestored('hasdata');
                            });
                            const notif = this.settingsLanguage == 'cn' ? '当前对话 { #' + old_chatid + ' } 在同步过程中被修改，已为您自动创建对话的副本 { #' + new_chatid + ' } 。\r\n当一个对话在多个标签页或窗口中同时被打开时，会出现这种情况。' : 'The chat { #' + old_chatid + ' } has been modified during synchronization, and a copy of the chat has been created { #' + new_chatid + ' } .\r\nThis can occur when a conversation is opened in multiple tabs/windows at once.';
                            const title = this.settingsLanguage == 'cn' ? '浏览器本地存储 - 同步 - 通知' : 'Browser Local Storage Sync Notification';
                            this.showNotification(notif, 'center', title);

                            // 2) 同步chatdata
                            // 见下方代码

                            // 3) 保存： 副本
                            this.saveChatData_Single(chatDataCopy); //单一的保存，不影响界面任何数据

                            //回到顶部
                            this.gotoTop_List();
                        }

                        // 同步chatdata （选择确定或取消都需要同步）
                        let chat_Data_modified = this.chatDataList.find(c => c.chatcreatedtime === chatdata.chatcreatedtime);//找出需要修改的对话
                        chatdata = JSON.parse(JSON.stringify(chatdata));
                        Object.assign(chat_Data_modified, chatdata); //更新替换，将chat_Data_modified同步更新为chatdata的数据

                        if (!need_to_create_copy) {
                            //7 读取 读取对话数据 还原聊天记录 恢复聊天设置
                            this.loadChatData(chat_Data_modified);

                            //其他读取后需要处理的
                            this.$nextTick(() => {
                                // 1 2 3 4 5 6
                                this.todoList_ChatDataRestored('hasdata', 50);
                            });
                        }
                    } else {
                        // 处理修改的数据，id 不等于 selectID
                        // 修改
                        // 同步chatdata
                        let chat_Data_modified = this.chatDataList.find(c => c.chatcreatedtime === chatdata.chatcreatedtime);//找出需要修改的对话
                        chatdata = JSON.parse(JSON.stringify(chatdata));
                        Object.assign(chat_Data_modified, chatdata); //更新替换，将chat_Data_modified同步更新为chatdata的数据
                    }
                })

                this.localStorageEvent_chatDataList_update_start = false; //更新结束

                this.localStorage_SaveData(null, 'chatDataList');//重新保存一次
            },

            //以下是一个JavaScript函数，将比较两个chatData_default对象是否完全相同（除了 chatlasttime、chatid 和 chattitle 属性）：
            //该函数接受两个chatData_default对象作为参数，并按属性比较它们。 如果两个对象具有相同的所有属性值（除了 chatlasttime、chatid 和 chattitle），则该函数将返回 true，否则返回 false。
            compareChatData(chatData1, chatData2) {
                const whitelist = ["chatlasttime", "chatid", "chattitle", "version", "msg"]; //增加到5个，加上version、msg(聊天输入框内容)
                for (let prop in chatData1) {
                    if (!whitelist.includes(prop) && JSON.stringify(chatData1[prop]) !== JSON.stringify(chatData2[prop])) {
                        return false;
                    }
                }
                return true;
            },

            // 创建副本时，生成新的不重复的chatcreatedtime
            newChatCreatedTime_ForCopyChatData(chatdata) {
                let copytime = new Date(chatdata.chatcreatedtime.replace(/-/g, "/")).getTime();//原始创建时间的时间戳
                copytime = copytime + 1000; //增加1秒
                let newcreatedtime = this.formatDateYYYYMMDDHHmmss(new Date(copytime), ':');
                //当前chatDataList = this.chatDataList
                //获取最新本地存储中的chatDataList
                let localStorage_chatDataList = this.localStorage_GetDataByKey('chatDataList');
                localStorage_chatDataList = localStorage_chatDataList == null ? [] : localStorage_chatDataList;

                // 循环判断是否存在冲突
                while (this.isTimeConflict(newcreatedtime, localStorage_chatDataList, this.chatDataList)) {
                    copytime += 1000; // 增加1秒
                    newcreatedtime = this.formatDateYYYYMMDDHHmmss(new Date(copytime), ':'); // 重新转换为日期字符串
                }
                return newcreatedtime;
            },

            // 判断时间是否与数组中的时间冲突  创建副本chatcreatedtime
            isTimeConflict(time, array1, array2) {
                for (let i = 0; i < array1.length; i++) {
                    if (array1[i].chatcreatedtime === time) {
                        return true;
                    }
                }
                for (let i = 0; i < array2.length; i++) {
                    if (array2[i].chatcreatedtime === time) {
                        return true;
                    }
                }
                return false;
            },

            //检测浏览器是否支持localStorage
            checkLocalStorageSupport() {
                const testKey = '__test__';
                const storage = window.localStorage;
                try {
                    // 检查是否支持 localStorage
                    if (!storage) {
                        return false;
                    }
                    // 检查是否可以写入、读取和删除数据
                    storage.setItem(testKey, testKey);
                    if (storage.getItem(testKey) !== testKey) {
                        return false;
                    }
                    storage.removeItem(testKey);
                    return true;
                } catch (e) {
                    const e_message = e.message ? e.message : '';
                    const e_name = e.name ? e.name : '';
                    this.localStorageExceedsMaximum(e_message, e_name);

                    return false;
                }
            },

            // localStorage 本地存储 保存数据到本地存储(只保存单条对话)  数据*4：用户设置+对话记录(当前对话)+密钥数据+提示词数据
            localStorage_SaveData(event, keyname) {

                if (!this.isLocalStorageSupported) {
                    return;//浏览器当前不支持localStorage时
                }
                if (!this.isLocalStorageAllowed) {
                    return;//不允许缓存时
                }
                if (this.isSendingNow || this.isCheckingApiKeyNow || this.isSendingNow_ForSmartMaxTokens) {//正在发送时 或 正在验证时
                    if (keyname == null || keyname == 'chatDataList' || keyname == 'apikeyData') {
                        return;
                    }
                }
                if (this.localStorageEvent_chatDataList_update_start) {
                    return;//正在标签页间同步数据
                }

                //keyname为空时，为自动保存，同时保存4类数据
                if (keyname == null) {
                    let SECONDS_IN_MS = 3000;
                    let timeSinceLastUpdate = Date.now() - this.localStorage_lastSaveTime || Infinity;
                    //从事件激活保存时，需要进行时间判断。 非事件激活，将立马保存（需要>=0.5秒）。
                    if (event && event.type) {
                        if (event.type == 'WindowBlur') {
                            timeSinceLastUpdate = Date.now() - this.localStorage_lastSaveTime_onblur || Infinity;
                            SECONDS_IN_MS = 10000;
                        } else if (event.type == 'pagehide' || event.type == 'unload' || event.type == 'beforeunload') {
                            SECONDS_IN_MS = 400;
                            this.localStorage_lastSaveTime_onblur = this.localStorage_lastSaveTime;
                        } else {
                            this.localStorage_lastSaveTime_onblur = this.localStorage_lastSaveTime;
                        }

                        if (timeSinceLastUpdate < SECONDS_IN_MS) { //距离上次小于3秒/10秒/0.4秒
                            //if(event && event.type){
                            //	console.log(`[${event.type}] - ${timeSinceLastUpdate /1000}s内已有其他事件激活了同步流程.`);
                            //}
                            return;
                        } else { //超过三秒钟/十秒钟则立即进行保存与更新。
                            //if(event && event.type){
                            //	console.log(`[${event.type}] - 正在保存到缓存中`);
                            //}
                            // nothing to do
                        }
                    } else {
                        SECONDS_IN_MS = 100;
                        if (timeSinceLastUpdate < SECONDS_IN_MS) { //距离上次小于0.1秒
                            return;
                        }
                    }
                }

                // - 1.用户设置 userSettings
                if (keyname == null || keyname == 'userSettings') {
                    this.saveUserSettings();// 保存UserSettings
                }

                // - 2.更新聊天数据ChatDataList(只更新当前对话 chatData)
                if (keyname == null || keyname == 'chatDataList') {
                    this.saveChatData_Selected();// 保存ChatData
                    this.chatDataList_Sort();// ChatDataList排序
                }

                // - 3.API Key数据  API Keys Data
                if (keyname == null || keyname == 'apikeyData') {
                    this.saveApikeyData();// 保存apikeyData
                }

                // - 4.提示词数据  Prompts Data
                if (keyname == null || keyname == 'promptsData') {
                    this.savePromptsData();// 保存Prompts Data
                }

                if (keyname == null) {
                    this.localStorage_lastSaveTime = Date.now(); //更新保存时间
                    this.localStorage_lastSaveTime_onblur = Date.now(); //更新保存时间
                }
            },

            // localStorage 本地存储 获取单独的数据
            localStorage_GetDataByKey(keyname) {
                if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                    return null;//浏览器当前不支持localStorage 或 不允许缓存
                } else {
                    try {
                        const data = JSON.parse(localStorage.getItem(keyname));
                        if (data != null) {
                            return data;
                        } else {
                            return keyname == 'chatDataList' ? [] : null; //chatDataList意外为null时，返回空数组
                        }
                    } catch (e) {
                        console.error('localStorage_GetDataByKey()', e);
                        this.showNotification('keyname:\r\n' + keyname + 'Error Name:\r\n' + e.name + '\r\n\r\nError Message:\r\n' + e.message, 'center', 'Error - localStorage_GetDataByKey()');
                        return null;
                    }
                }
            },

            // localStorage 本地存储 恢复本地存储数据  数据*4：用户设置+对话记录+密钥数据+提示词数据
            localStorage_RestoreData() {
                // 读取
                let userSettings = this.localStorage_GetDataByKey('userSettings');
                let chatDataList = this.localStorage_GetDataByKey('chatDataList');
                let apikeyData = this.localStorage_GetDataByKey('apikeyData');
                let promptsData = this.localStorage_GetDataByKey('promptsData');
                //console.log('localStorage_RestoreData() - userSettings: ',userSettings);
                //console.log('localStorage_RestoreData() - chatDataList: ',chatDataList);
                //console.log('localStorage_RestoreData() - apikeyData: ',apikeyData);
                //console.log('localStorage_RestoreData() - promptsData: ',promptsData);

                if (userSettings === null && (chatDataList === null || chatDataList.length === 0) && apikeyData === null && promptsData === null) {
                    return false;
                }

                // 版本兼容性处理 - none
                // 日期：7.09  userSettings - 新增变量 pageScaling（移动设备页面比例）


                // - Start 1.
                // 还原用户设置数据 userSettings
                this.RestoreData_userSettings(userSettings);
                // - End 1.

                // - 3.
                // 批量还原API Key数据  API Keys Data
                apikeyData = apikeyData == null ? {} : apikeyData;
                this.setVariablesToThis(apikeyData, this.apikeyData_default, true);//根据变量列表和默认值 还原变量
                //删除apikey为空的项（不排除有空值，v7.10之前：1.初次验证前apiCheckedData会有空项。 2.本地存储也会存储空项）
                this.apiCheckedData = this.apiCheckedData.filter(item => item.apikey.trim() !== '');
                //循环this.apiCheckedData 验证数据 & 兼容性处理
                //判断属性是否存在且数据是否合法，如果缺少属性 或 属性数据不合法，就补上属性和默认值
                this.validateApiCheckedData(this.apiCheckedData);

                // - 4.
                // 批量还原提示词数据  Prompts Data
                promptsData = promptsData == null ? {} : promptsData;
                this.setVariablesToThis(promptsData, this.promptsData_default, true);//根据变量列表和默认值 还原变量

                // - 2. chat数据   *调到在 3、4 之后执行
                this.chatDataList = chatDataList == null ? [] : chatDataList;
                this.chatDataList_Sort();// ChatDataList排序
                const selectedChatData = (this.chatDataList == null || this.chatDataList[0] == null) ? {} : JSON.parse(JSON.stringify(this.chatDataList[0])); //this.chatDataList[0] 最新一次对话
                this.loadChatData(selectedChatData); //读取对话数据 还原聊天记录 恢复聊天设置

                //设置首次读取localStorage = false
                this.islocalStorageFirstLoad = false;
                return true;
            },

            // 还原用户设置数据 userSettings
            RestoreData_userSettings(userSettings) {
                if (userSettings === null) {
                    return;
                }
                // - Start 1.
                // 用户设置 user变量
                const userVariables = userSettings.userVariables == null ? {} : userSettings.userVariables; // user变量
                this.setVariablesToThis(userVariables, this.userVariables_default, true);//根据变量列表和默认值 还原变量

                // 获取头像URL
                let isErr = (userSettings.userAvatarURL == null || userSettings.userAvatarURL.trim() == '');
                const userAvatarURL = isErr ? this.userAvatarURL : userSettings.userAvatarURL; //*注意：需要“const userAvatarURL” ，而不能直接赋值给this.userAvatarURL= 因为可能是无效的 赋值等于是直接加载图片 无效的图片会报错
                //isErr = ( userSettings.gptAvatarURL == null || userSettings.gptAvatarURL.trim() == '');
                isErr = (userSettings.gptAvatarURL == null);
                const gptAvatarURL = isErr ? this.gptAvatarURL : userSettings.gptAvatarURL;

                // 转换用户头像 base64 To Blob
                const avatar = userSettings.avatar;
                try {
                    //仅首次读取localStorage时，需要重新转换、生成全新的blob Url。
                    if (this.islocalStorageFirstLoad && avatar && avatar != '') {
                        const arr = avatar.split(',');
                        const type = arr[0].match(/:(.*?);/)[1];
                        const bstr = atob(arr[1]);
                        let n = bstr.length;
                        const u8arr = new Uint8Array(n);
                        while (n--) {
                            u8arr[n] = bstr.charCodeAt(n);
                        }

                        this.userAvatarUp_base64 = avatar;
                        this.userAvatarUp_blob = new Blob([u8arr], {type});
                        this.userAvatarUp_blobUrl = URL.createObjectURL(this.userAvatarUp_blob);
                    }
                } catch (e) {
                    console.error("userSettings.avatar转换过程出错");
                    this.userAvatarUp_base64 = '';
                    this.userAvatarUp_blob = null;
                    this.userAvatarUp_blobUrl = '';
                }

                // 用户头像 User Avatar
                //首次读取localStorage时 && 头像为上传的图片时 && blob Url为有效的blob地址时(转换出错时可能为空)
                if (this.islocalStorageFirstLoad && userAvatarURL.startsWith('blob:') && this.userAvatarUp_blobUrl.startsWith('blob:')) {
                    this.inputUserImageUrl = JSON.parse(JSON.stringify(this.userAvatarUp_blobUrl)); //全新的blob Url
                } else {
                    this.inputUserImageUrl = userAvatarURL;//不变，保持上次的设置。可能是：网址、本地路径、上传图片的仍有效的blob url
                }

                // GPT头像 GPT Avatar
                //首次读取localStorage时
                const greenSVG = this.settingsLanguage == 'cn' ? this.GPTGreenSVG_UrlText_cn : this.GPTGreenSVG_UrlText_en;
                if (this.islocalStorageFirstLoad) {
                    if (gptAvatarURL.startsWith('blob:') || gptAvatarURL == '') { //上次是上传头像 && 上次是默认GPT的GreenSVG头像
                        this.inputGPTImageUrl = greenSVG;
                    } else {
                        this.inputGPTImageUrl = gptAvatarURL; //不变，保持上次的设置。可能是：网址、本地路径
                    }
                } else {
                    this.inputGPTImageUrl = this.isgptGreenSvgShow ? greenSVG : gptAvatarURL; //不变，保持上次的设置。可能是：网址、本地路径、GreenSVG、上传的仍有效的图片的blob url
                }

                //更新用户、GPT头像
                this.updateAvatar('user', false);//保存 更新 save update  false=Don't show notif.
                this.updateAvatar('gpt', false);

                // - End 1.
            },

            // 验证和修正apiCheckedData的数据结构和数据合法性。 恢复数据时 / 保存数据时 / 导入数据时
            validateApiCheckedData(apicheckedcata) {
                // v7.18 如果您需要手动修改apikeyData的JSON文件进行批量添加API Key，只需要记录apikey一个属性即可，导入JSON文件，代码会自动添加其他属性，并填入默认值。
                // v7.18 If you need to manually modify the JSON file of apikeyData for bulk addition of API Keys, you only need to record the apikey attribute. Import the JSON file and the code will automatically add other attributes and fill in the default values.

                //v7.18 循环apicheckedcata 验证数据 & 兼容性处理
                //判断属性是否存在且数据是否合法，如果缺少属性 或 属性数据不合法，就补上属性和默认值
                apicheckedcata.forEach(apiData => {
                    apiData.apikey = typeof apiData.apikey === 'string' ? apiData.apikey : ''; //Not null，Default:''
                    apiData.apiurl = typeof apiData.apiurl === 'string' ? apiData.apiurl : ''; //Not null，Default:''  *v7.18 not used
                    apiData.date1 = Number.isInteger(apiData.date1) ? apiData.date1 : 0; //Not null，Int，Default:0
                    apiData.date2 = Number.isInteger(apiData.date2) ? apiData.date2 : 0; //Not null，Int，Default:0
                    apiData.date3 = Number.isInteger(apiData.date3) ? apiData.date3 : 0; //Not null，Int，Default:0
                    apiData.nexttime = Number.isInteger(apiData.nexttime) ? apiData.nexttime : 0;  //Not null，Int，Default:0

                    apiData.level = apiData.level != null && typeof apiData.level === 'number' ? apiData.level : null;  //Allowing null，null or Int，Int:01234589，Default:null

                    //......apiData.freq = Number.isInteger(apiData.freq) ? apiData.freq : 0; xxxxxxxx number
                    apiData.freq = typeof apiData.freq === 'number' && !isNaN(apiData.freq) ? apiData.freq : 0; //Not null，number，Default:0  *v7.18 not used
                    apiData.vpn = typeof apiData.vpn === 'string' ? apiData.vpn : ''; //Not null，Default:''  *v7.18 not used
                    apiData.owner = typeof apiData.owner === 'string' ? apiData.owner : 'me'; //Not null，Default:''  *v7.18 not used
                    apiData.succcount = Number.isInteger(apiData.succcount) ? apiData.succcount : 0; //Not null，Int，Default:0
                    apiData.failcount = Number.isInteger(apiData.failcount) ? apiData.failcount : 0; //Not null，Int，Default:0
                    //......apiData.succrate = Number.isInteger(apiData.succrate) ? apiData.succrate : 0; xxxxxxxx number
                    apiData.succrate = typeof apiData.succrate === 'number' && !isNaN(apiData.succrate) ? apiData.succrate : 0; //Not null，number，Default:0
                    apiData.updatetime = Number.isInteger(apiData.updatetime) ? apiData.updatetime : 0; //Not null，Int，Default:0
                });
            },

            // 批量还原变量到this  variables要还原的变量   variables_default变量的列表/默认值   isusedefault 缺失数据时是否使用默认值
            setVariablesToThis(variables, variables_default, isusedefault) {
                Object.keys(variables_default).forEach(key => {
                    if (variables_default.hasOwnProperty(key)) {
                        if (variables.hasOwnProperty(key)) {
                            this[key] = variables[key];
                        } else {
                            if (isusedefault === true) {
                                this[key] = JSON.parse(JSON.stringify(variables_default[key]));
                            }
                        }
                    }
                });
            },

            // 从this中生成/获取变量 返回变量{}  variables_default变量的列表
            getVariablesFromThis(variables_default) {
                const variables = {};
                Object.keys(variables_default).forEach(key => {
                    if (variables_default.hasOwnProperty(key)) {
                        variables[key] = this[key]
                    }
                })
                return variables;
            },

            // 保存用户设置 UserSettings
            saveUserSettings() {
                const userVariables = this.getVariablesFromThis(this.userVariables_default);

                const userSettings = {
                    version: this.userSettingsVersion,
                    avatar: this.userAvatarUp_base64,
                    userAvatarURL: this.userAvatarURL,
                    gptAvatarURL: this.gptAvatarURL,
                    userVariables
                };
                try {
                    //保存到localStorage
                    localStorage.setItem('userSettings', JSON.stringify(userSettings));
                } catch (e) {
                    const e_message = e.message ? e.message : '';
                    const e_name = e.name ? e.name : '';
                    this.localStorageExceedsMaximum(e_message, e_name);
                }
            },

            // 保存API Keys Data  更新合并最新数据，唯一不足：多标签不同步删除，需要删除时，应该只保留一个标签页。否则其他未关闭的标签页大概率会恢复已删除的key。
            saveApikeyData() {
                //获取最新API Keys Data
                let localStorage_apikeyData = this.localStorage_GetDataByKey('apikeyData');
                //获取最新已验证的apikey数组数据 apiCheckedData
                let storage_apiCheckedData = localStorage_apikeyData == null ? [] : localStorage_apikeyData.apiCheckedData;
                storage_apiCheckedData = storage_apiCheckedData == null ? [] : storage_apiCheckedData;
                storage_apiCheckedData = storage_apiCheckedData.filter(apidata => apidata.apikey && apidata.apikey.trim() !== '');//排除空项

                // 循环storage_apiCheckedData 验证数据 & 兼容性处理
                //判断属性是否存在且数据是否合法，如果缺少属性 或 属性数据不合法，就补上属性和默认值
                this.validateApiCheckedData(storage_apiCheckedData);

                // 合并更新数据到this.apiCheckedData
                const newData = this.apiCheckedData.filter(apidata => apidata.apikey && apidata.apikey.trim() !== '');//排除空项

                //更新差异数据， 新增 或 更新
                storage_apiCheckedData.forEach(apidata => {
                    const existingApiIndex = newData.findIndex(api => api.apikey === apidata.apikey);
                    if (existingApiIndex !== -1) {//存在-更新
                        const existingApi = newData[existingApiIndex];
                        // 比较updatetime，以更新最新的数据
                        if (apidata.updatetime > existingApi.updatetime) {
                            newData[existingApiIndex] = apidata;
                        }
                    } else {//不存在-新增
                        newData.push(apidata);
                    }
                });
                this.apiCheckedData = newData;

                const apikeyData = this.getVariablesFromThis(this.apikeyData_default);
                //手动增加版本
                apikeyData.version = this.apikeyData_Version;
                try {
                    //保存到localStorage
                    localStorage.setItem('apikeyData', JSON.stringify(apikeyData));
                } catch (e) {
                    const e_message = e.message ? e.message : '';
                    const e_name = e.name ? e.name : '';
                    this.localStorageExceedsMaximum(e_message, e_name);
                }
            },

            // 保存提示词 Prompts Data
            savePromptsData() {
                //获取最新Prompts Data
                let localStorage_promptsData = this.localStorage_GetDataByKey('promptsData');
                //获取最新prompts数组数据 gptPrompts
                let gptPrompts = localStorage_promptsData == null ? [] : localStorage_promptsData.gptPrompts;
                //
                //提示词 待开发111111111

                const promptsData = this.getVariablesFromThis(this.promptsData_default);
                //手动增加版本
                promptsData.version = this.promptsData_Version;
                try {
                    //保存到localStorage
                    localStorage.setItem('promptsData', JSON.stringify(promptsData));
                } catch (e) {
                    const e_message = e.message ? e.message : '';
                    const e_name = e.name ? e.name : '';
                    this.localStorageExceedsMaximum(e_message, e_name);
                }
            },

            // 保存当前对话 ChatData
            saveChatData_Selected() {
                //获取当前chatdata
                let chat_Data = this.getSelectedChatData();
                if (chat_Data == null || chat_Data.chatcreatedtime == null) {
                    return; //chatcreatedtime == null 新对话不保存
                }

                //如果当前不是新对话，但是存在新对话/空对话，需要在刷新List后给补上
                const chatdata_null = this.chatDataList.find(c => c.chatcreatedtime === null);

                //获取最新chatDataList
                let localStorage_chatDataList = this.localStorage_GetDataByKey('chatDataList');
                if (localStorage_chatDataList == null) {
                    return;
                }

                //*正常情况下，无需去null。 如果意外存在新对话(chatcreatedtime=null)，顺便去除
                localStorage_chatDataList = localStorage_chatDataList.filter(c => c.chatcreatedtime !== null);

                //通过当前的chat_Data.chatcreatedtime(对话的创建时间，唯一值)找到对话 *gpt还推荐个方法:使用find 和Object.assign
                const chatdataIndex = localStorage_chatDataList.findIndex((chatdata) => chatdata.chatcreatedtime === chat_Data.chatcreatedtime);
                if (chatdataIndex !== -1) {
                    localStorage_chatDataList[chatdataIndex] = chat_Data;// 存在时，更新
                } else {
                    localStorage_chatDataList.unshift(chat_Data); // 不存在时，新增
                }

                try {
                    //保存到localStorage
                    localStorage.setItem('chatDataList', JSON.stringify(localStorage_chatDataList));
                } catch (e) {
                    const e_message = e.message ? e.message : '';
                    const e_name = e.name ? e.name : '';
                    this.localStorageExceedsMaximum(e_message, e_name);
                }

                //保存后，还原可能存在的新对话/空对话
                if (chatdata_null != null) {
                    localStorage_chatDataList.unshift(chatdata_null);//还原当前标签页的新对话
                }

                //同步this.chatDataList
                this.chatDataList = localStorage_chatDataList;
            },

            // 保存单条对话 ChatData 不改变不刷新this.chatDataList
            saveChatData_Single(chatdata) {
                if (chatdata == null || chatdata.chatcreatedtime == null) {
                    return; //chatcreatedtime == null 新对话不保存
                }

                //获取最新chatDataList
                let localStorage_chatDataList = this.localStorage_GetDataByKey('chatDataList');
                if (localStorage_chatDataList == null) {
                    return;
                }

                //通过当前的chatdata.chatcreatedtime(对话的创建时间，唯一值)找到对话 *gpt还推荐个方法:使用find 和Object.assign
                const chatdataIndex = localStorage_chatDataList.findIndex((c) => c.chatcreatedtime === chatdata.chatcreatedtime);
                if (chatdataIndex !== -1) {
                    localStorage_chatDataList[chatdataIndex] = chatdata;// 存在时，更新
                } else {
                    localStorage_chatDataList.unshift(chatdata); // 不存在时，新增
                }

                try {
                    //保存到localStorage
                    localStorage.setItem('chatDataList', JSON.stringify(localStorage_chatDataList));
                } catch (e) {
                    const e_message = e.message ? e.message : '';
                    const e_name = e.name ? e.name : '';
                    this.localStorageExceedsMaximum(e_message, e_name);
                }
            },

            // *禁用本地存储时  保存单条对话 更新List  ChatData  NoLocalStorage
            saveChatData_NoLocalStorage(chatdata) {
                let chat_Data = chatdata;
                if (chatdata == null) {
                    //获取当前chatdata
                    chat_Data = this.getSelectedChatData();
                }
                if (chat_Data == null || chat_Data.chatcreatedtime == null) {
                    return; //chatcreatedtime == null 新对话不保存
                }

                //通过当前的chat_Data.chatcreatedtime(对话的创建时间，唯一值)找到对话
                const chatdataIndex = this.chatDataList.findIndex((chatdata) => chatdata.chatcreatedtime === chat_Data.chatcreatedtime);
                if (chatdataIndex !== -1) {
                    this.chatDataList[chatdataIndex] = chat_Data;// 存在时，更新
                } else {
                    this.chatDataList.unshift(chat_Data); // 不存在时，新增
                }

                //排序
                this.chatDataList_Sort();
            },

            // chatDataList排序
            chatDataList_Sort() {
                if (this.chatDataList != null) {
                    //排序 将最后活动时间转为时间戳后，进行排序，降序，新在上，null置顶。 {safari不认"yyyy-MM-dd HH:mm:ss"格式。通过replace(/-/g, "/") 转格式为yyyy/MM/dd HH:mm:ss ，兼容apple产品 ios macos。} {如果 chatlasttime 为空，可以使用 new Date('9999-12-31') 来表示一个较晚的日期。}
                    this.chatDataList.sort((a, b) => {
                        const aTime = a.chatlasttime ? new Date(a.chatlasttime.replace(/-/g, "/")).getTime() : new Date('9999-12-31').getTime();
                        const bTime = b.chatlasttime ? new Date(b.chatlasttime.replace(/-/g, "/")).getTime() : new Date('9999-12-31').getTime();
                        return bTime - aTime;
                    });
                }
            },

            // 获取当前对话数据 return chatData
            getSelectedChatData() {
                let chatVariables = this.getVariablesFromThis(this.chatVariables_default);
                chatVariables = JSON.parse(JSON.stringify(chatVariables));

                // *6.21 不要直接使用this.selectedChatData（未同步，未手动更新）
                let chatData = {
                    version: this.chatDataList_Version,
                    chatcreatedtime: this.chatcreatedtime,
                    chatlasttime: this.chatlasttime,
                    chatid: this.chatid,
                    chattitle: this.chattitle,

                    msgList: this.msgList,
                    msgContent: this.msgContent,
                    msgTokens: this.msgTokens,
                    msgContentForMsgList_SingleRound: this.msgContentForMsgList_SingleRound,
                    msg: this.msg,
                    chatVariables

                };

                chatData = JSON.parse(JSON.stringify(chatData)); //防止双向绑定
                return chatData;
            },

            // 对话列表窗口 - 点击对话
            chatList_ClickChatData(chatdata) {
                let notif = '';
                if (chatdata.chatcreatedtime === this.chatcreatedtime) { //this.selectedChatData.chatcreatedtime
                    return; //已经选中，重复点击
                }

                //0 正忙，稍后再操作
                if (this.checkBusyStatus('ForChat')) {
                    //底部会出现通知tooltips。 如果返回 true，正在发送 或 正在验证 或 正在标签页间同步数据
                    return;
                }

                //1 先保存当前data *会获取最新List
                this.localStorage_SaveData(null, 'chatDataList');//保存
                //禁用本地存储时的保存
                if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                    this.saveChatData_NoLocalStorage(null);
                }

                //chatdata可能不是最新，从List中取最新
                const chat_Data = this.chatDataList.find(c => c.chatcreatedtime === chatdata.chatcreatedtime);

                //7 读取 读取对话数据 还原聊天记录 恢复聊天设置
                this.loadChatData(chat_Data);

                //其他读取后需要处理的
                this.$nextTick(() => {
                    // 1 2 3 4 5
                    this.todoList_ChatDataRestored('hasdata');
                });
                notif = this.settingsLanguage == 'cn' ? '已切换对话' : 'Chat is switched';
                this.showNotification(notif, 'bottom');
            },

            // 加载/读取对话数据 还原聊天记录 恢复聊天设置
            loadChatData(chatdata) {
                this.selectedChatData = chatdata == null ? {} : JSON.parse(JSON.stringify(chatdata));//防止双向绑定
                let chatVariables = this.selectedChatData.chatVariables == null ? {} : this.selectedChatData.chatVariables; // chat变量
                chatVariables = JSON.parse(JSON.stringify(chatVariables));

                //操作数据之前，先删除可能存在的记忆分割线 <hr>标签 分割线是独立存在的 非绑定 会一直存在 需要先删除
                const hrElements = document.querySelectorAll('hr.newcontext-hr');
                for (let i = 0; i < hrElements.length; i++) {
                    hrElements[i].remove();
                }
                //操作数据之前，先删除之前页面上的所有的气泡框中的一键复制按钮(气泡框+内部可能存在的代码块) <button>标签 一键复制是独立存在的 非绑定 重复渲染会出现重复按钮 需要先删除
                //重叠的按钮在界面上看不出来，当使用一键复制时，每个按钮都会成为一个空行，影响复制的内容
                const msgcopydiv = document.querySelectorAll('.msgcopydiv');
                for (let i = 0; i < msgcopydiv.length; i++) {
                    const copybtn = msgcopydiv[i].querySelectorAll('.copybtn');
                    for (let j = 0; j < copybtn.length; j++) {
                        copybtn[j].remove();
                    }
                }

                // 批量还原chat变量  *防绑定
                this.setVariablesToThis(chatVariables, this.chatVariables_default, true);//根据变量列表和默认值 还原变量

                // 批量还原chat data变量  *防绑定  //需要排除：变量、版本，this.chatData_default列表中已排除
                this.setVariablesToThis(JSON.parse(JSON.stringify(this.selectedChatData)), this.chatData_default, false);//根据变量列表和默认值 还原变量

                // 改变按钮状态 *延迟10ms 首次加载网页时，加延迟确保apikey数据先读取完，避免apikey数据未准备好导致按钮状态错误
                setTimeout(() => {
                    this.inputapichange(false); //false=不弹通知
                }, 10);

                // 保存提示词按钮 设为可用
                this.btnDisabledState_SetSystemPrompt = false;

            },

            // 对话列表窗口 - 修改Chat ID
            chatList_EditChatID(chatdata) {
                event.stopPropagation();

                //0 正忙，稍后再操作
                if (this.checkBusyStatus('ForChat')) {
                    //底部会出现通知tooltips。 如果返回 true，正在发送 或 正在验证 或 正在标签页间同步数据
                    return;
                }
                //1 先保存当前data *会获取最新的List (this.chatDataList)
                this.localStorage_SaveData(null, 'chatDataList');//保存

                //弹窗前 延时0.1秒  *同步缓冲
                setTimeout(() => {
                    // 根据id查找对应的对话  *数据会绑定
                    const chat_Data = this.chatDataList.find(c => c.chatcreatedtime === chatdata.chatcreatedtime);
                    if (chat_Data) {
                        // 显示弹窗，允许用户输入新的ID
                        let newID = null;
                        const maxLength = 20; //可以不做限制，20并无特殊意义
                        const regex = /[^\x00-\xff]/g; // 匹配中日韩等文字或全角符号的正则表达式
                        let text = this.settingsLanguage == 'cn' ? '修改对话ID：' : 'Edit Chat ID:';
                        let err = false;
                        this.is_Alert_Confirm_Prompt_Open = true;
                        while (newID === null || newID.replace(regex, "aa").length > maxLength) {
                            newID = newID === null ? (chat_Data.chatid == null ? '' : chat_Data.chatid) : newID;
                            if (err) {
                                text = this.settingsLanguage == 'cn' ? '修改对话ID，最大 ' + maxLength + ' 个字符(中文为10个字)：' : 'Edit Chat ID, maximum length is ' + maxLength + ' .(some characters may take up more than one character space):';
                            }
                            newID = window.prompt(text, newID);
                            if (newID === null) {
                                break; // 用户点击了取消按钮，结束循环
                            }
                            err = true;
                        }
                        setTimeout(() => {
                            this.is_Alert_Confirm_Prompt_Open = false;
                        }, 100);

                        //弹窗后 延时0.2秒  *同步缓冲
                        setTimeout(() => {

                            if (newID !== null) { //chat_Data修改后，this.chatDataList中对应数据会自动更新

                                //第二个定时器会导致绑定失灵(回车或点击确认后 不会立即更新)，重新再绑定一次chat_Data
                                const chat_Data = this.chatDataList.find(c => c.chatcreatedtime === chatdata.chatcreatedtime);

                                chat_Data.chatid = newID.trim();
                                if (chat_Data.chatcreatedtime === this.chatcreatedtime) { //this.selectedChatData.chatcreatedtime
                                    this.chatid = newID.trim();
                                }

                                if (chat_Data.chatcreatedtime != null) { //如果修改对象不是新对话，那么手动保存到localStorage
                                    ////将最新的this.chatDataList（需要去null）保存到localStorage
                                    //const newChatDataList =  this.chatDataList.filter(c => c.chatcreatedtime !== null);
                                    //localStorage.setItem('chatDataList', JSON.stringify(newChatDataList));
                                    this.saveChatData_Single(chat_Data); //单一的保存，不影响界面任何数据
                                }

                                const notif = this.settingsLanguage == 'cn' ? '修改成功' : 'Modification successful';
                                this.showNotification(notif, 'bottom');
                            }
                            //延时0.2秒
                        }, 200);
                    }
                    //延时0.1秒
                }, 100);
            },

            // 对话列表窗口 - 修改Chat Title
            chatList_EditChatTitle(chatdata) {
                event.stopPropagation();

                //0 正忙，稍后再操作
                if (this.checkBusyStatus('ForChat')) {
                    //底部会出现通知tooltips。 如果返回 true，正在发送 或 正在验证 或 正在标签页间同步数据
                    return;
                }
                //1 先保存当前data *会获取最新的List (this.chatDataList)
                this.localStorage_SaveData(null, 'chatDataList');//保存

                //弹窗前 延时0.1秒  *同步缓冲
                setTimeout(() => {
                    // 根据id查找对应的对话  *数据会绑定
                    let chat_Data = this.chatDataList.find(c => c.chatcreatedtime === chatdata.chatcreatedtime);
                    if (chat_Data) {
                        // 显示弹窗，允许用户输入新的标题
                        let newTitle = null;
                        const maxLength = 46; //可以不做限制，46并无特殊意义
                        const regex = /[^\x00-\xff]/g; // 匹配中日韩等文字或全角符号的正则表达式
                        let text = this.settingsLanguage == 'cn' ? '修改对话标题：' : 'Edit Chat Title:';
                        let err = false;
                        this.is_Alert_Confirm_Prompt_Open = true;
                        while (newTitle === null || newTitle.replace(regex, "aa").length > maxLength) {
                            newTitle = newTitle === null ? chat_Data.chattitle : newTitle;
                            if (err) {
                                text = this.settingsLanguage == 'cn' ? '修改对话标题，最大 ' + maxLength + ' 个字符(中文为23个字)：' : 'Edit Chat Title, max ' + maxLength + ' characters (some characters may take up more than one character space):';
                            }
                            newTitle = window.prompt(text, newTitle);
                            if (newTitle === null) {
                                break; // 用户点击了取消按钮，结束循环
                            }
                            err = true;
                        }
                        setTimeout(() => {
                            this.is_Alert_Confirm_Prompt_Open = false;
                        }, 100);

                        //弹窗后 延时0.2秒  *同步缓冲
                        setTimeout(() => {

                            if (newTitle !== null) { //chat_Data修改后，this.chatDataList中对应数据会自动更新

                                //第二个定时器会导致绑定失灵(回车或点击确认后 不会立即更新)，重新再绑定一次chat_Data
                                chat_Data = this.chatDataList.find(c => c.chatcreatedtime === chatdata.chatcreatedtime);

                                chat_Data.chattitle = newTitle.trim();
                                if (chat_Data.chatcreatedtime === this.chatcreatedtime) { //this.selectedChatData.chatcreatedtime
                                    this.chattitle = newTitle.trim();
                                }

                                if (chat_Data.chatcreatedtime != null) { //如果修改对象不是新对话，那么手动保存到localStorage
                                    ////将最新的this.chatDataList（需要去null）保存到localStorage
                                    //const newChatDataList =  this.chatDataList.filter(c => c.chatcreatedtime !== null);
                                    //localStorage.setItem('chatDataList', JSON.stringify(newChatDataList));
                                    this.saveChatData_Single(chat_Data); //单一的保存，不影响界面任何数据
                                }

                                const notif = this.settingsLanguage == 'cn' ? '修改成功' : 'Modification successful';
                                this.showNotification(notif, 'bottom');
                            }
                            //延时0.2秒
                        }, 200);
                    }
                    //延时0.1秒
                }, 100);
            },

            // 对话列表窗口 - 新的对话  或  加载网页无本地数据时的初始化
            chatList_addNewChatData(isblankpage) {
                let notif = '';
                const isBlank_OnPageLoad = (isblankpage == event || isblankpage == null) ? false : isblankpage;

                if (!isBlank_OnPageLoad) { //(非加载新页面) 点击添加新对话时
                    //0 正忙，稍后再操作
                    if (this.checkBusyStatus('ForChat')) {
                        //底部会出现通知tooltips。 如果返回 true，正在发送 或 正在验证 或 正在标签页间同步数据
                        return;
                    }
                    //1 当前非新对话，则先保存。 当前为新对话，则返回。
                    if (this.chatcreatedtime != null) { //this.selectedChatData.chatcreatedtime
                        this.localStorage_SaveData(null, 'chatDataList');//保存
                        //禁用本地存储时的保存
                        if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                            this.saveChatData_NoLocalStorage(null);
                        }
                        //当前虽然不是新对话，但是列表里存在新的对话
                        const nullIndex = this.chatDataList.findIndex((chatdata) => chatdata.chatcreatedtime === null);
                        if (nullIndex !== -1) {
                            notif = this.settingsLanguage == 'cn' ? '列表中已存在空白对话，是否继续生成新的对话？' : 'New blank chat exists in the list. Continue creating a new chat?';
                            this.is_Alert_Confirm_Prompt_Open = true;
                            const confirmed = confirm(notif); //用户选择是否继续，继续会删除之前的空白对话
                            if (!confirmed) {
                                setTimeout(() => {
                                    this.is_Alert_Confirm_Prompt_Open = false;
                                }, 100);
                                return;//取消 将返回
                            }
                            setTimeout(() => {
                                this.is_Alert_Confirm_Prompt_Open = false;
                            }, 100);
                        }
                    } else {
                        if (this.chatDataList.length > 0) {
                            notif = this.settingsLanguage == 'cn' ? '当前已经是新的对话' : 'A new chat has already been started.';
                            this.showNotification(notif, 'bottom');
                            return; //返回，只能存在一个新的对话
                        } else {
                            //length==0，意外为空时，继续执行。正常情况下不会出现这种情况
                        }
                    }
                }

                //2 新对话的默认变量 默认值
                const chatVariables = JSON.parse(JSON.stringify(this.chatVariables_default));

                //3 新对话chatdata默认设置
                let chatData = JSON.parse(JSON.stringify(this.chatData_default));
                chatData.version = this.chatDataList_Version;//手动添加版本号
                chatData.chatVariables = chatVariables; //手动添加chatVariables

                //变更chatid  = 初始化新ID
                let newChatId = 1;
                while (this.chatDataList.some((chatdata) => parseInt(chatdata.chatid) === newChatId)) {
                    newChatId++;
                }
                chatData.chatid = newChatId.toString(); //Chat ID *可重复

                if (!isBlank_OnPageLoad) { //非加载新页面 & 存在本地数据时
                    //4 如果存在null的chatdata，先删除，再新增
                    this.chatDataList = this.chatDataList.filter(c => c.chatcreatedtime !== null);
                    //5 排序
                    this.chatDataList_Sort();
                }

                //6 新增
                this.chatDataList.unshift(chatData);
                //7 读取 读取对话数据 还原聊天记录 恢复聊天设置
                this.loadChatData(this.chatDataList[0]); //此时chatcreatedtime为null，一定是[0]。*错误的日期，比如导入2025年的chatdata，也不会影响[0]

                //8 针对新对话/空对话 其他单独的设置/变量：
                //其他读取后需要处理的
                this.$nextTick(() => {
                    // 1 3 4 5 7
                    this.todoList_ChatDataRestored('new');
                });

                if (!isBlank_OnPageLoad) { //(非网页加载) 点击添加新对话时
                    notif = this.settingsLanguage == 'cn' ? '新对话开始' : 'New chat started';
                    this.showNotification(notif, 'bottom');
                }
            },

            // chat data还原后需要处理的
            todoList_ChatDataRestored(mode, notiftime) {
                //1 首条 欢迎语
                this.initTheFirstMessage(); //First Message

                //2 (有数据时) 恢复样式 更新样式
                if (mode == 'hasdata') {
                    this.add_hr_For_ChatDataRestored(); //先手动添加分割线
                    this.changeStyle_Time_WechatStyle();
                    this.changeStyle_FontSize(); //6.20 暂时无内容
                    this.changeStyle_Time(); //6.20 暂时无内容
                    this.changeStyle_DividerLine(); //最后，是否显示分割线

                    //v7.17 当聊天输入框有内容时，需要重新记录maxHeight，刷新后或切换对话后，点击输入框才能恢复正确的高度   max-height maxHeight
                    let textarea_Chat = document.querySelector('.textareachatinputbox');
                    const scrollHeight = textarea_Chat.scrollHeight;
                    const height6px = parseInt(scrollHeight) + 6; // +
                    if (parseInt(scrollHeight) <= 38) {
                        textarea_Chat.style.maxHeight = '38px';
                    } else if (parseInt(scrollHeight) < 450) {
                        //textarea_Chat.style.maxHeight = scrollHeight + 'px'; // -
                        textarea_Chat.style.maxHeight = height6px.toString() + 'px'; // +
                    } else {
                        textarea_Chat.style.maxHeight = '456px'; // + 6
                    }
                }

                //3 改变按钮文字
                const isuser = this.msgList[this.msgList.length - 1]["my"];
                if (!isuser && (this.sentext == '请重发' || this.sentext == 'Please Retry')) {
                    this.sentext = this.settingsLanguage == 'cn' ? '发送' : 'Send';
                }
                if (isuser && (this.sentext == '发送' || this.sentext == 'Send')) {
                    this.sentext = this.settingsLanguage == 'cn' ? '请重发' : 'Please Retry';
                }

                //4 批量添加一键复制按钮 全页面(含首条)
                this.initAddCopyButtonForMessage();

                //5 更新第一条消息/欢迎语的datetime
                const firstDatetime = this.formatDateYYYYMMDDHHmmss(new Date(), ':');
                this.msgList[0]['datetime'] = this.msgList[0]['datetime'] == "2000-01-01 00:00:00" ? firstDatetime : this.msgList[0]['datetime']; //不等于2000年的时间，说明读取了缓存
                this.msgContentForMsgList_SingleRound[0]['datetime'] = this.msgContentForMsgList_SingleRound[0]['datetime'] == "2000-01-01 00:00:00" ? firstDatetime : this.msgContentForMsgList_SingleRound[0]['datetime'];

                //6 (有数据时) 延迟滚动
                if (notiftime != null && typeof notiftime === 'number') {
                    //延迟****毫秒，滚动到最下方
                    setTimeout(() => {
                        this.scrollToBottomView();
                    }, notiftime);
                } else {
                    //其他情况不延迟。排除新对话
                    if (mode != 'new') {
                        this.scrollToBottomView();
                    }
                }

                //7 (新的对话) 非中文 改# Chat History
                if (mode == 'new') {
                    if (this.settingsLanguage != 'cn') {
                        this.chathistory = '# Chat History  \r\n\r\n';
                    }
                }
            },

            // 对话列表窗口 - 更多操作：创建副本 清空  *tips：手机/移动端，点击下拉框后会获得焦点（一定是当前对话）。电脑端不会获得焦点，不一定是当前对话。
            changeSelectedChatListMoreOptions(chatdata) {
                event.stopPropagation();
                if (this.checkBusyStatus('ForChat')) {
                    this.selectedChatListMoreOptions = ''; //复原
                    //底部会出现通知tooltips。 如果返回 true， 正在发送 或 正在验证 或 正在标签页间同步数据
                    return;
                }

                let optionsStr = this.selectedChatListMoreOptions;
                this.selectedChatListMoreOptions = ''; //复原
                const iscurrentdata = (this.chatcreatedtime === chatdata.chatcreatedtime); //this.selectedChatData.chatcreatedtime


                if (optionsStr === 'CreateCopy') {
                    // 2) 创建副本
                    //1 先保存当前data  刷新ChatDataList
                    this.localStorage_SaveData(null, 'chatDataList');//保存
                    //禁用本地存储时的保存
                    if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                        this.saveChatData_NoLocalStorage(null);
                    }

                    //延时0.15秒  *同步 缓冲
                    setTimeout(() => {
                        let chat_Data = this.chatDataList.find(c => c.chatcreatedtime === chatdata.chatcreatedtime);//确保chatdata数据最新，主要针对copy当前对话的情况
                        let chatDataCopy = JSON.parse(JSON.stringify(chat_Data));
                        chatDataCopy.chatcreatedtime = this.newChatCreatedTime_ForCopyChatData(chat_Data);//生成新的chatcreatedtime
                        chatDataCopy.chatlasttime = this.formatDateYYYYMMDDHHmmss(new Date(), ':'); //现在的时间
                        chatDataCopy.chatid += this.settingsLanguage == 'cn' ? ' 副本' : ' Copy';

                        //新增到List
                        this.chatDataList.unshift(chatDataCopy);

                        //读取 读取对话数据 还原聊天记录 恢复聊天设置
                        this.loadChatData(chatDataCopy);

                        //其他读取后需要处理的
                        this.$nextTick(() => {
                            // 1 2 3 4 5
                            this.todoList_ChatDataRestored('hasdata');
                        });

                        // 3) 保存： 副本
                        this.saveChatData_Single(chatDataCopy); //单一的保存，不影响界面任何数据
                        //禁用本地存储时的保存
                        if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                            //前面已经有新增到List的代码  this.saveChatData_NoLocalStorage(chatDataCopy);
                        }

                        this.chatDataList_Sort();// ChatDataList排序

                        const notif = this.settingsLanguage == 'cn' ? '成功创建 { #' + chatDataCopy.chatid + ' }，当前对话为 { #' + chat_Data.chatid + ' } 的副本' : 'Copy of chat { #' + chatDataCopy.chatid + ' } created successfully. It is a replica of chat { #' + chat_Data.chatid + ' } .';
                        this.showNotification(notif, 'bottom', '', 5500);

                        //回到顶部
                        this.gotoTop_List();

                        //延时0.15秒
                    }, 150);

                } else if (optionsStr === 'ClearPrevious') {
                    // 3) 清空记录（保留最近单轮对话） Clear the history (Keep the latest round)
                    //1 先保存当前data  刷新ChatDataList
                    this.localStorage_SaveData(null, 'chatDataList');//保存
                    //禁用本地存储时的保存
                    if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                        this.saveChatData_NoLocalStorage(null);
                    }

                    //延时0.15秒  *同步 缓冲
                    setTimeout(() => {
                        //chatdata
                        let chat_Data = this.chatDataList.find(c => c.chatcreatedtime === chatdata.chatcreatedtime);//确保chatdata数据最新，主要针对当前对话的情况
                        chat_Data.chatlasttime = this.formatDateYYYYMMDDHHmmss(new Date(), ':'); //现在的时间

                        // 处理msgList
                        const numLastMsgs = chat_Data.msgContentForMsgList_SingleRound.length - 1; //最后一轮对话list的长度，-1 去掉首条欢迎语，剩下就是msg的数量，包含失败的msg
                        if (chat_Data.msgList.length > 1) {
                            if (numLastMsgs == 0) {
                                chat_Data.msgList = [chat_Data.msgList[0]];
                            } else {
                                const lastMsgs = chat_Data.msgList.slice(-numLastMsgs); //截取最后一轮 *numLastMsgs不能为0，为0时会返回整个chat_Data.msgList
                                chat_Data.msgList = [chat_Data.msgList[0]].concat(lastMsgs); //拼接: 首条 + 最后一轮
                            }
                        }

                        //非当前对话
                        //变量保持不变，仅chathistory需要单独处理
                        //处理chathistory
                        let history = chat_Data.chatVariables.chathistory;
                        const historyhead = this.settingsLanguage == 'cn' ? '# GPT聊天记录 / Chat History  \r\n\r\n' : '# Chat History  \r\n\r\n';
                        const dividerLineTxt1 = this.settingsLanguage == 'cn' ? '#### #无记忆状态，新对话开始\r\n***\r\n\r\n' : '#### #The context was emptied. New Chat begins.\r\n***\r\n\r\n';
                        const dividerLineTxt2 = this.settingsLanguage == 'cn' ? '#### #无记忆状态，新对话开始\r\n***\r\n\r\n' : '#### #The context was emptied. New Chat begins.\r\n***\r\n\r\n';
                        let lastHeadIndex = history.lastIndexOf(dividerLineTxt1); //找正常语言...
                        let dividerLineTxtLength = dividerLineTxt1.length;
                        if (lastHeadIndex === -1) {
                            lastHeadIndex = history.lastIndexOf(dividerLineTxt2); //找不到，就找另一种...
                            dividerLineTxtLength = dividerLineTxt2.length;
                        }
                        if (lastHeadIndex !== -1) {
                            chat_Data.chatVariables.chathistory = history.substring(lastHeadIndex + dividerLineTxtLength);//从历史记录中最后一次分割线末尾开始截取
                            chat_Data.chatVariables.chathistory = historyhead + chat_Data.chatVariables.chathistory;//加上head
                        } else { //两次都没匹配到
                            //chat_Data.chatVariables.chathistory就是最后一轮，无需改变
                        }

                        //如果是当前对话
                        if (iscurrentdata) {
                            //读取 刷新
                            this.loadChatData(chat_Data);
                            //保存当前data *会获取最新List
                            this.localStorage_SaveData(null, 'chatDataList');//保存
                            //禁用本地存储时的保存
                            if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                                this.saveChatData_NoLocalStorage(null);
                            }
                            //其他读取后需要处理的
                            this.$nextTick(() => {
                                // 1 2 3 4 5 6
                                this.todoList_ChatDataRestored('hasdata', 50);
                            });

                            //回到顶部
                            this.gotoTop_List();
                        } else {

                            this.saveChatData_Single(chat_Data); //单一的保存，不影响界面任何数据
                            //禁用本地存储时的保存
                            if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                                this.saveChatData_NoLocalStorage(chat_Data);
                            }
                            this.chatDataList_Sort();// ChatDataList排序
                        }
                        //延时0.15秒
                    }, 150);

                } else if (optionsStr === 'ClearAll') {
                    // 4) 清空所有记录 Clear the history
                    //1 先保存当前data  刷新ChatDataList
                    this.localStorage_SaveData(null, 'chatDataList');//保存
                    //禁用本地存储时的保存
                    if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                        this.saveChatData_NoLocalStorage(null);
                    }

                    //延时0.15秒  *同步 缓冲
                    setTimeout(() => {
                        //chatdata
                        let chat_Data = this.chatDataList.find(c => c.chatcreatedtime === chatdata.chatcreatedtime);//确保chatdata数据最新，主要针对当前对话的情况

                        // chatVariables
                        const temp_hasSystemPromptBeenSaved = chat_Data.chatVariables.hasSystemPromptBeenSaved;//是否保存过提示词 保持现状
                        const temp_gptSystemPromptReadOnly = chat_Data.chatVariables.gptSystemPromptReadOnly;//提示词内容(只读)  保持现状

                        chat_Data.chatVariables = JSON.parse(JSON.stringify(this.chatVariables_default));//恢复默认
                        chat_Data.chatVariables.hasSystemPromptBeenSaved = temp_hasSystemPromptBeenSaved; //是否保存过提示词  恢复
                        chat_Data.chatVariables.gptSystemPromptReadOnly = temp_gptSystemPromptReadOnly; //提示词内容(只读)  恢复

                        if (this.settingsLanguage != 'cn') {
                            chat_Data.chatVariables.chathistory = '# Chat History  \r\n\r\n';
                        }

                        //手动将“首条提示词信息”追加到chat_Data.chatVariables.chathistory中
                        if (this.gptSystemPrompt_hidden.trim() == '') { //不存在隐形提示词
                            const sys_prompt = chat_Data.chatVariables.gptSystemPromptReadOnly;
                            //以下代码注释 详见addSystemPromptToChatHistory
                            let hasMultipleLines = /\r?\n\r?\n/.test(sys_prompt);
                            let backticks = this.findMaxBackticks(sys_prompt);
                            hasMultipleLines = backticks != '' ? true : hasMultipleLines;
                            backticks = backticks == '' ? '````' : backticks;

                            let addMarkdownSyntaxStart = hasMultipleLines ? ('\r\n' + backticks + '\r\n') : '\r\n> ';  // (> 后面有个空格)
                            let addMarkdownSyntaxEnd = hasMultipleLines ? ('\r\n' + backticks + '\r\n\r\n') : '\r\n\r\n';
                            let nullprompt = '';  //提示词为空的情况
                            if (sys_prompt == '') {
                                nullprompt = this.settingsLanguage == 'cn' ? ': 空' : ': None';
                                addMarkdownSyntaxStart = '\r\n';
                                addMarkdownSyntaxEnd = '\r\n';
                            }

                            const head = this.settingsLanguage == 'cn' ? '### #系统提示词 system prompt ' : '### #System Prompt ';
                            chat_Data.chatVariables.chathistory += (head + nullprompt + addMarkdownSyntaxStart + sys_prompt + addMarkdownSyntaxEnd);
                        }

                        // chatdata
                        chat_Data.chatlasttime = this.formatDateYYYYMMDDHHmmss(new Date(), ':'); //现在的时间
                        chat_Data.msgList = [chat_Data.msgList[0]];
                        chat_Data.msgContent = [chat_Data.msgContent[0]];
                        chat_Data.msgTokens = [chat_Data.msgTokens[0]];
                        chat_Data.msgContentForMsgList_SingleRound = [chat_Data.msgContentForMsgList_SingleRound[0]];

                        if (iscurrentdata) {

                            //读取 刷新
                            this.loadChatData(chat_Data);
                            //保存当前data *会获取最新List
                            this.localStorage_SaveData(null, 'chatDataList');//保存
                            //禁用本地存储时的保存
                            if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                                this.saveChatData_NoLocalStorage(null);
                            }
                            //其他读取后需要处理的
                            this.$nextTick(() => {
                                // 1 3 4 5
                                this.todoList_ChatDataRestored('ClearAll');
                            });

                            //回到顶部
                            this.gotoTop_List();
                        } else {

                            this.saveChatData_Single(chat_Data); //单一的保存，不影响界面任何数据
                            //禁用本地存储时的保存
                            if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                                this.saveChatData_NoLocalStorage(chat_Data);
                            }
                            this.chatDataList_Sort();// ChatDataList排序
                        }
                        //延时0.15秒
                    }, 150);
                } else if (optionsStr === 'ExportChatData') {
                    // 1) 导出单个对话 Export Chat Data
                    //1 先保存当前data  刷新ChatDataList
                    this.localStorage_SaveData(null, 'chatDataList');//保存
                    //禁用本地存储时的保存
                    if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                        this.saveChatData_NoLocalStorage(null);
                    }

                    //延时0.15秒  *同步 缓冲
                    setTimeout(() => {
                        let fileName = this.exportJSONFileName;
                        let id_fileName = chatdata.chatid == null ? '' : chatdata.chatid.toString();
                        const temp = '(#' + id_fileName + ')';
                        notif = this.settingsLanguage == 'cn' ? '正在导出JSON文件 - 单个对话数据' : 'Exporting JSON file - Single Chat Data';
                        notif += temp;

                        const newChatDataList = this.chatDataList.filter(c => c.chatcreatedtime === chatdata.chatcreatedtime);//仅保留1个对话
                        let dataObject = {};
                        dataObject['chatDataList'] = newChatDataList;
                        const dataString = JSON.stringify(dataObject);
                        fileName += 'SingleChatData'; //文件名
                        // ID & 过滤非法字符(文件名)
                        id_fileName = id_fileName.replace(/[\\/:\*\?"<>\|]/g, '');
                        id_fileName = id_fileName.replace(/[\/\?<>\\:\*\|":]/g, '');
                        id_fileName = id_fileName.trim() === '' ? '' : id_fileName;
                        id_fileName = '(ID ' + id_fileName + ')';
                        fileName += id_fileName;

                        this.showNotification(notif, 'bottom', '', 2800);
                        //导出
                        this.ExportData_JSON(dataString, fileName);
                        //延时0.15秒
                    }, 150);
                } else if (optionsStr === 'EraseLogs') {
                    // 5) 抹掉日志 Erase Logs
                    //1 先保存当前data  刷新ChatDataList
                    this.localStorage_SaveData(null, 'chatDataList');//保存
                    //禁用本地存储时的保存
                    if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                        this.saveChatData_NoLocalStorage(null);
                    }

                    //延时0.15秒  *同步 缓冲
                    setTimeout(() => {
                        //chatdata
                        let chat_Data = this.chatDataList.find(c => c.chatcreatedtime === chatdata.chatcreatedtime);//确保chatdata数据最新，主要针对当前对话的情况
                        chat_Data.chatlasttime = this.formatDateYYYYMMDDHHmmss(new Date(), ':'); //现在的时间

                        // chatVariables   logs <=> chathistory
                        const historyhead = this.settingsLanguage == 'cn' ? '# GPT聊天记录 / Chat History  \r\n\r\n' : '# Chat History  \r\n\r\n';
                        const eraseLogsStr = this.settingsLanguage == 'cn' ? '### #于 ' + chat_Data.chatlasttime + ' 手动抹掉日志\r\n***\r\n\r\n' : '### #Manually erase logs on ' + chat_Data.chatlasttime + '\r\n***\r\n\r\n';
                        chat_Data.chatVariables.chathistory = historyhead + eraseLogsStr; //清空，重置Logs(chathistory)

                        if (iscurrentdata) {
                            //读取 刷新
                            this.loadChatData(chat_Data);
                            //保存当前data *会获取最新List
                            this.localStorage_SaveData(null, 'chatDataList');//保存
                            //禁用本地存储时的保存
                            if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                                this.saveChatData_NoLocalStorage(null);
                            }
                            //其他读取后需要处理的
                            this.$nextTick(() => {
                                // 1 3 4 5
                                this.todoList_ChatDataRestored('ClearAll');
                            });

                            //回到顶部
                            this.gotoTop_List();
                        } else {

                            this.saveChatData_Single(chat_Data); //单一的保存，不影响界面任何数据
                            //禁用本地存储时的保存
                            if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                                this.saveChatData_NoLocalStorage(chat_Data);
                            }
                            this.chatDataList_Sort();// ChatDataList排序
                        }

                        const notif = this.settingsLanguage == 'cn' ? '已抹掉对话 { #' + chat_Data.chatid + ' } 的日志' : 'The logs for chat { #' + chat_Data.chatid + ' } have been erased';
                        this.showNotification(notif, 'bottom', '', 3500);
                        //延时0.15秒
                    }, 150);
                }

                if (optionsStr == 'ClearPrevious' || optionsStr == 'ClearAll') {
                    const chat_id = chatdata.chatid.toString().trim() != '' ? ('#' + chatdata.chatid.toString() + ' ') : ''; //chatdata.chatid.toString().trim() 若不加.toString()，在iphone-safari中会报错，mac-safari不会报错。
                    const typestr = optionsStr == 'ClearAll' ? '' : (this.settingsLanguage == 'cn' ? ' (保留最近单轮对话)' : ' (Keep the latest round)');
                    const notif = this.settingsLanguage == 'cn' ? chat_id + '对话已清空' + typestr : chat_id + 'Chat has been cleared' + typestr;
                    this.showNotification(notif, 'bottom', '', 2700);
                }

                // 移除焦点
                event.target.blur();
            },

            // 对话列表窗口 - ChatList goto TOP
            gotoTop_List() {
                setTimeout(() => {
                    const positioningDiv = document.querySelector('#dialog-ChatList .dialog-content');
                    positioningDiv.scrollIntoView({behavior: 'smooth', block: 'start'});
                }, 160); //在此之前可能有聊天页面的滚动（todoList_ChatDataRestored中的滚动，一般是50ms），如果不延迟会冲突/失败，100ms也可能失败，这里设为160ms
            },
            // 对话列表窗口 - goto TOP
            gotoTop() {
                event.stopPropagation();
                const positioningDiv = document.getElementById('app');
                this.$nextTick(() => {
                    positioningDiv.scrollIntoView({behavior: 'smooth', block: 'start'});
                });
                const notif = this.settingsLanguage == 'cn' ? '已定位到最早的消息' : 'Reached earliest message';
                this.showNotification(notif, 'bottom');
            },
            // 对话列表窗口 - goto Bottom
            gotoBottom() {
                event.stopPropagation();
                this.scrollToBottomView();
                const notif = this.settingsLanguage == 'cn' ? '已定位到最新消息' : 'Reached latest message';
                this.showNotification(notif, 'bottom');
            },

            // 对话列表窗口 - 对话 删除
            deleteChatData(chatdata) {
                event.stopPropagation();
                const iscurrentdata = (this.chatcreatedtime === chatdata.chatcreatedtime);//this.selectedChatData.chatcreatedtime
                if (this.checkBusyStatus('ForChat')) {
                    //底部会出现通知tooltips。 如果返回 true，正在发送 或 正在验证 或 正在标签页间同步数据
                    return;
                }

                //1 先保存当前data *会获取最新的List (this.chatDataList)
                this.localStorage_SaveData(null, 'chatDataList');//保存
                //禁用本地存储时的保存
                if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                    this.saveChatData_NoLocalStorage(null);
                }

                //弹窗前 延时0.1秒  *同步缓冲
                setTimeout(() => {
                    let notif = this.settingsLanguage == 'cn' ? '您确定要删除这个对话吗？此操作无法撤销。' : 'Are you sure you want to delete this chat? This action cannot be undone.';
                    if (chatdata.chatcreatedtime !== null) {
                        notif += this.settingsLanguage == 'cn' ? '\r\n\r\#' : '\r\n\r\n#';
                        notif += chatdata.chatid;
                        if (chatdata.chattitle != '') {
                            notif += this.settingsLanguage == 'cn' ? '\r\n#' : '\r\n#';
                            notif += chatdata.chattitle;
                        }
                        notif += this.settingsLanguage == 'cn' ? '\r\n上次活动时间：' : '\r\nLast Activity Time: ';
                        notif += chatdata.chatlasttime;
                        notif += this.settingsLanguage == 'cn' ? '\r\n对话创建时间：' : '\r\nChat Creation Time: ';
                        notif += chatdata.chatcreatedtime;
                    }
                    this.is_Alert_Confirm_Prompt_Open = true;
                    const confirmed = confirm(notif); //用户选择是否继续，继续会删除对话
                    if (!confirmed) {
                        setTimeout(() => {
                            this.is_Alert_Confirm_Prompt_Open = false;
                        }, 100);
                        return;//取消 将返回
                    }
                    setTimeout(() => {
                        this.is_Alert_Confirm_Prompt_Open = false;
                    }, 100);

                    //弹窗后 延时0.1秒
                    setTimeout(() => {

                        //**弹窗期间数据很大概率会改变，需要重新获取一次List (this.chatDataList)
                        //如果当前不是新对话，但是存在新对话/空对话，需要在刷新List后给补上
                        const chatdata_null = this.chatDataList.find(c => c.chatcreatedtime === null);
                        //获取最新chatDataList
                        let localStorage_chatDataList = this.localStorage_GetDataByKey('chatDataList');
                        localStorage_chatDataList = localStorage_chatDataList == null ? [] : localStorage_chatDataList;
                        //保存后，还原可能存在的新对话/空对话
                        if (chatdata_null != null) {
                            localStorage_chatDataList.unshift(chatdata_null);//还原当前标签页的新对话
                        }
                        if (this.isLocalStorageAllowed && this.isLocalStorageSupported) {
                            //同步this.chatDataList
                            this.chatDataList = localStorage_chatDataList;
                        }
                        this.chatDataList_Sort();// ChatDataList排序
                        let index = this.chatDataList.findIndex((c) => c.chatcreatedtime === chatdata.chatcreatedtime);//找出序号

                        this.chatDataList = this.chatDataList.filter(c => c.chatcreatedtime !== chatdata.chatcreatedtime); //删除目标对话，保留其他对话，顺序不会改变
                        notif = this.settingsLanguage == 'cn' ? '对话已删除' : 'Chat has been deleted';
                        this.showNotification(notif, 'bottom');
                        try {
                            if (iscurrentdata) {
                                if (this.chatDataList == [] || this.chatDataList.length == 0) {
                                    //将最新的this.chatDataList保存到localStorage
                                    if (this.isLocalStorageAllowed && this.isLocalStorageSupported) {
                                        localStorage.setItem('chatDataList', JSON.stringify(this.chatDataList));
                                    }

                                    //延迟1秒弹消息
                                    setTimeout(() => {
                                        notif = this.settingsLanguage == 'cn' ? '列表空，已自动添加一个新的对话' : 'The list is empty. A new chat has been automatically added.';
                                        this.showNotification(notif, 'bottom', '', 2900);
                                    }, 1400);
                                    //添加新对话
                                    this.chatList_addNewChatData(true);
                                } else {
                                    //将最新的this.chatDataList（需要去null）保存到localStorage
                                    const newChatDataList = this.chatDataList.filter(c => c.chatcreatedtime !== null);
                                    if (this.isLocalStorageAllowed && this.isLocalStorageSupported) {
                                        localStorage.setItem('chatDataList', JSON.stringify(newChatDataList));
                                    }

                                    index = index - 1; //被删的前一位
                                    index = index < 0 ? 0 : index;
                                    index = index < this.chatDataList.length ? index : 0; // <=(this.chatDataList.length - 1)
                                    //7 读取 读取对话数据 还原聊天记录 恢复聊天设置
                                    this.loadChatData(this.chatDataList[index]);

                                    //其他读取后需要处理的
                                    this.$nextTick(() => {
                                        // 1 2 3 4 5 6
                                        this.todoList_ChatDataRestored('hasdata', 100);
                                    });
                                }
                            } else {
                                //将最新的this.chatDataList（需要去null）保存到localStorage
                                const newChatDataList = this.chatDataList.filter(c => c.chatcreatedtime !== null);
                                if (this.isLocalStorageAllowed && this.isLocalStorageSupported) {
                                    localStorage.setItem('chatDataList', JSON.stringify(newChatDataList));
                                }
                            }
                            this.chatDataList_Sort();// ChatDataList排序
                        } catch (e) {
                            const e_message = e.message ? e.message : '';
                            const e_name = e.name ? e.name : '';
                            this.localStorageExceedsMaximum(e_message, e_name);
                        }
                        //延时0.1秒
                    }, 100);
                    //延时0.1秒
                }, 100);
            },

            // 对话列表窗口 - 改变最大宽度 *移动端可能会需要
            changeChatListMaxWidth() {
                let chatListDiv = document.querySelector("#dialog-ChatList .dialog");
                let notif = this.settingsLanguage == 'cn' ? '最大宽度已恢复' : 'Max-Width restored';

                if (chatListDiv == null) {
                    return;
                }

                let maxWidth = chatListDiv.style.maxWidth;
                if (maxWidth == '430px') {
                    chatListDiv.style.maxWidth = '800px';
                    chatListDiv.style.width = '87%';
                    notif = this.settingsLanguage == 'cn' ? '最大宽度已调至最大' : 'Max-Width adjusted to maximum';
                } else if (maxWidth == '800px') {
                    chatListDiv.style.maxWidth = '430px';
                    chatListDiv.style.width = '95%';
                } else {
                    chatListDiv.style.maxWidth = '430px';
                    chatListDiv.style.width = '95%';
                }

                this.showNotification(notif, 'bottom', '', 2300);
            },

            // 对话列表窗口 - 改变透明度
            changeChatListTransparent() {
                let chatListDiv = document.querySelector("#dialog-ChatList .dialog");
                let notif = this.settingsLanguage == 'cn' ? '已恢复为不透明' : 'Restored to opacity';

                if (chatListDiv == null) {
                    return;
                }

                let color = chatListDiv.style.backgroundColor;
                if (color == 'rgb(237, 237, 237)') {
                    this.isChatListTransparent = true;
                    chatListDiv.style.backgroundColor = 'rgba(237, 237, 237, 0.03)';
                    notif = this.settingsLanguage == 'cn' ? '已变更为透明' : 'Changed to transparent';
                } else if (color == 'rgba(237, 237, 237, 0.03)') {
                    this.isChatListTransparent = false;
                    chatListDiv.style.backgroundColor = 'rgb(237, 237, 237)';
                } else {
                    this.isChatListTransparent = false;
                    chatListDiv.style.backgroundColor = 'rgb(237, 237, 237)';
                }

                this.showNotification(notif, 'bottom');
            },


            // 提示词窗口 - 复制系统提示词到编辑区
            copySystemPromptToEditor() {
                let sys_prompt = this.gptSystemPromptReadOnly.trim();
                let notif = '';
                if (sys_prompt == '') {
                    notif = this.settingsLanguage == 'cn' ? '当前的系统提示词为空' : 'The current system prompt is empty';
                    this.showNotification(notif, 'bottom');
                    return;
                }

                if (this.gptSystemPrompt.trim() != '') {
                    notif = this.settingsLanguage == 'cn' ? '提示词编辑框中已存在内容，确认覆盖吗？' : 'Content exists in Prompt Editor, confirm overwrite?';
                    if (!confirm(notif)) {
                        return; // 用户点击了“取消”按钮
                    }
                }

                this.gptSystemPrompt = sys_prompt;
                this.btnDisabledState_SetSystemPrompt = true;
                notif = this.settingsLanguage == 'cn' ? '已复制到编辑区' : 'Copied to the Prompt Editor';
                this.showNotification(notif, 'bottom', '', 2000);
            },
            // 提示词窗口 - 改变最大宽度
            changePromptGeneratorMaxWidth() {
                let promptsDiv = document.querySelector("#dialog-promptgenerator .dialog");
                let notif = this.settingsLanguage == 'cn' ? '适合移动设备的宽度' : 'Optimized for mobile devices';

                if (promptsDiv == null) {
                    return;
                }

                let width = promptsDiv.style.width;
                if (width == '95%') {
                    promptsDiv.style.width = '75%';
                    promptsDiv.style.maxWidth = '75%';
                    notif = this.settingsLanguage == 'cn' ? '适合更大分辨率的宽度' : 'Optimized to support larger screens';
                } else if (width == '75%') {
                    promptsDiv.style.width = '95%';
                    promptsDiv.style.maxWidth = '800px';
                } else {
                    promptsDiv.style.width = '95%';
                    promptsDiv.style.maxWidth = '800px';
                }

                this.showNotification(notif, 'bottom', '', 2300);
            },
            // 提示词窗口 - 改变布局 *移动端可能会需要
            changePromptGeneratorLayout() {
                let notif = this.settingsLanguage == 'cn' ? '布局切换成功' : 'Layout changed';
                if (this.styleDialogLayout.padding == '20px 20px 20px 20px') {
                    this.styleDialogLayout.padding = '20px 20px 20px 45px';
                } else if (this.styleDialogLayout.padding == '20px 20px 20px 45px') {
                    this.styleDialogLayout.padding = '20px 45px 20px 20px';
                } else {
                    this.styleDialogLayout.padding = '20px 20px 20px 20px';
                }

                this.showNotification(notif, 'bottom');
            },
            // 提示词窗口 - 改变指定元素的高度
            changeElementHeight(classname) {
                let notif = this.settingsLanguage == 'cn' ? '高度已调整' : 'Height adjusted';
                let input = document.querySelector('.' + classname);
                if (input == null) {
                    return;
                }

                let h = input.style.height;
                if (h == '100px') {
                    input.style.height = '250px';
                } else if (h == '250px') {
                    input.style.height = '500px';
                    notif = this.settingsLanguage == 'cn' ? '高度已调至最大' : 'Height adjusted to maximum';
                } else {
                    input.style.height = '100px';
                    notif = this.settingsLanguage == 'cn' ? '高度已恢复' : 'Height restored';
                }

                this.showNotification(notif, 'bottom');
            },
            // 提示词窗口 - 改变指定元素视图（是否自动换行）
            changeElementLineBreak(classname) {
                let notif = this.settingsLanguage == 'cn' ? '切换为 不自动换行' : 'Changed to no-wrap';
                let input = document.querySelector('.' + classname);
                if (input == null) {
                    return;
                }

                let whiteSpace = input.style.whiteSpace;
                if (whiteSpace != 'pre' && whiteSpace != 'pre-wrap') {
                    input.style.whiteSpace = 'pre';
                } else if (whiteSpace == 'pre') {
                    input.style.whiteSpace = 'pre-wrap';
                    notif = this.settingsLanguage == 'cn' ? '切换为 自动换行' : 'Changed to auto-wrap';
                } else {
                    input.style.whiteSpace = 'pre';
                }
                this.showNotification(notif, 'bottom');
            },

            // 提示词窗口 - 按钮：保存为系统提示词 更新系统提示词 Save system prompt
            setSystemPrompt() {

                let notif = this.settingsLanguage == 'cn' ? '保存成功' : 'Saved successfully';
                let notiftime = 1450;

                if (this.checkBusyStatus()) {
                    // 如果返回 true，正在发送或正在验证
                    return;
                }

                //隐形提示词 当gptSystemPrompt_hidden隐藏提示词存在时，不允许提交修改。 已禁用提示词弹窗，其实这里可以不写。
                if (this.gptSystemPrompt_hidden.trim() != '') {
                    notif = this.settingsLanguage == 'cn' ? '保存失败，代码中存在隐藏提示词' : 'Save failed, there is a hidden "system prompt" in the code.';
                    notiftime = 4000;
                    this.showNotification(notif, 'bottom', '', notiftime);
                    return;
                }

                // v6.12 允许保存空的提示词


                this.btnDisabledState_SetSystemPrompt = true;
                this.msgContent[0] = {"role": "system", "content": this.gptSystemPrompt.trim()};
                this.msgContentForMsgList_SingleRound[0] = {"role": "system", "content": this.gptSystemPrompt.trim()};
                this.gptSystemPromptReadOnly = this.gptSystemPrompt.trim();
                if (this.gptSystemPrompt.trim() === '') {
                    notif = this.settingsLanguage == 'cn' ? '保存成功，已取消系统提示词' : 'Save successful. System Prompt has been cancelled';
                    notiftime = 3500;
                }

                //为false，表示新窗口从未写入过指令[是否设置了默认system prompt]
                if (!this.hasSystemPromptBeenSaved) {
                    this.hasSystemPromptBeenSaved = true;
                    this.addSystemPromptToChatHistory('### #默认的系统提示词 system prompt ', '### #Default System Prompt '); //this.chathistory
                } else {
                    this.addSystemPromptToChatHistory('### #新的系统提示词 system prompt ', '### #New System Prompt '); //this.chathistory
                }

                //更新 保存 聊天数据 chatData chatDataList
                //更新最后活动时间
                this.updateChatLastTime(this.formatDateYYYYMMDDHHmmss(new Date(), ':'));//最后活动时间

                this.showNotification(notif, 'bottom', '', notiftime);

            },

            // 设置中点击打开 自动轮换API Key窗口 Auto Key Rotation
            clickOpenAutoKeyRotation() {
                let input = document.querySelector('.textareabulkaddapikey');

                //显示弹窗前
                if (this.apiCheckedData.length < 1) { //无可用Key时，自动显示批量添加
                    this.isShowBulkAddPanel_AutoKeyRotation = true;//显示批量
                    const notif = this.settingsLanguage == 'cn' ? '当前无可用的 API Key' : 'No available API Keys currently';
                    this.showNotification(notif, 'bottom', '', 1800);
                } else if (input != null && input.value.trim() === '') { //有可用Key && input && 批量添加无内容时
                    this.isShowBulkAddPanel_AutoKeyRotation = false;//不显示批量
                }

                //显示弹窗
                this.isShowDialog_AutoKeyRotation = true;
            },

            // 自动轮换API Key窗口 Auto Key Rotation - 切换排序 Sort
            changeAutoKeyRotationSorted() {
                if (this.checkBusyStatus()) {
                    // 如果返回 true，正在发送或正在验证
                    return;
                }

                this.changeAutoKeyRotationSorted_Mode++; // 0 1 2
                this.changeAutoKeyRotationSorted_Mode = this.changeAutoKeyRotationSorted_Mode > 2 ? 0 : this.changeAutoKeyRotationSorted_Mode;
                let notif;

                if (this.changeAutoKeyRotationSorted_Mode === 1) {
                    this.apiCheckedData.sort((a, b) => {
                        if (a.level !== b.level) {
                            return a.level - b.level; //级别升序
                        } else {
                            return a.nexttime - b.nexttime; //级别相同，则按预估可用时间升序
                        }
                    });
                    notif = this.settingsLanguage == 'cn' ? '按级别排序。 *排序对自动轮换功能没有影响' : 'Sort by level.  * Sorting has no impact on auto-rotation.';
                } else if (this.changeAutoKeyRotationSorted_Mode === 2) {
                    this.apiCheckedData.sort((a, b) => {
                        if (b.succrate !== a.succrate) {
                            return b.succrate - a.succrate; //成功率 降序
                        } else if (a.level !== b.level) {
                            return a.level - b.level; //成功率相同，则按级别升序
                        } else {
                            return a.nexttime - b.nexttime; //级别也相同，则按预估可用时间升序
                        }
                    });
                    notif = this.settingsLanguage == 'cn' ? '按成功率排序。 *排序对自动轮换功能没有影响' : 'Sort by success rate.  * Sorting has no impact on auto-rotation.';

                } else if (this.changeAutoKeyRotationSorted_Mode === 0) {
                    this.apiCheckedData.sort((a, b) => b.updatetime - a.updatetime);//最后更新时间 降序
                    notif = this.settingsLanguage == 'cn' ? '按更新时间排序。 *排序对自动轮换功能没有影响' : 'Sort by update time.  * Sorting has no impact on auto-rotation.';
                } else {
                    this.changeAutoKeyRotationSorted_Mode = 1;
                    notif = this.settingsLanguage == 'cn' ? '按级别排序。 *排序对自动轮换功能没有影响' : 'Sort by level.  * Sorting has no impact on auto-rotation.';
                }

                this.showNotification(notif, 'bottom', '', 3300);
            },

            // 自动轮换API Key窗口 Auto Key Rotation - 显示  批量添加   Display  Bulk Add
            autoKeyRotation_BulkAdd() {
                let notif = this.settingsLanguage == 'cn' ? '开始批量添加API Key' : 'Start bulk adding API Keys';

                if (this.isShowBulkAddPanel_AutoKeyRotation) {
                    notif = this.settingsLanguage == 'cn' ? '批量添加已准备就绪' : 'Bulk adding is ready';
                }

                this.isShowBulkAddPanel_AutoKeyRotation = true;
                this.showNotification(notif, 'bottom', '', 1800);

                setTimeout(() => { //滚动到Top  gotoTOP
                    const positioningDiv = document.querySelector('#dialog-AutoKeyRotation .dialog-content');
                    positioningDiv.scrollIntoView({behavior: 'smooth', block: 'start'});
                }, 0);
            },

            // 自动轮换API Key窗口 Auto Key Rotation - 批量添加 Bulk Add - 1）提交按钮 Submit Button  2）Initialize Default ApiKeysArray
            submitBulkApiKeys(isManuallySubmit, isshownotif, apiKeyArray) {
                if (this.checkBusyStatus()) {
                    // 如果返回 true，正在发送或正在验证
                    return;
                }
                isManuallySubmit = isManuallySubmit == null ? true : isManuallySubmit; //类别：是否手动点击提交
                isshownotif = isshownotif == null ? true : isshownotif; //是否通知消息
                apiKeyArray = apiKeyArray == null ? [] : apiKeyArray; //Api Key字符串数组

                //保存 获取最新API Key数据
                this.saveApikeyData();

                let notif = '';//通知内容
                let input = document.querySelector('.textareabulkaddapikey');//批量输入框
                //1) 手动点击提交按钮
                if (isManuallySubmit && input == null) { //bug
                    console.error("submitBulkApiKeys() - document.querySelector('.textareabulkaddapikey') == null ");
                    return;
                }
                //1) 手动点击提交按钮
                if (isManuallySubmit && input.value.trim() === '') { //无内容时
                    notif = this.settingsLanguage == 'cn' ? '内容为空' : 'Content is empty';
                    this.showNotification(notif, 'bottom');
                    return;
                }


                //检测是否包含API Key
                const apiKeyPattern = /^sk-[a-zA-Z0-9]+$/; // sk- 开头，不检测长度
                const apiKeyPattern_51 = /^sk-[a-zA-Z0-9]{48}$/; // sk- 后面48个字母和数字，一共51
                let validApiKeys = [];//成功的
                const invalidApiKeys = [];//失败的
                let validApiKeys_Not51 = [];//成功的,非51位

                //1) 手动点击提交按钮
                if (isManuallySubmit) {
                    //拆分 + 过滤空行 + 每行去空格
                    apiKeyArray = input.value.trim().split('\n');
                }

                apiKeyArray = apiKeyArray.filter(apiKey => apiKey.trim() !== '').map(apiKey => apiKey.trim());

                apiKeyArray.forEach((apiKey) => { //
                    //apiKey = apiKey.trim(); 上面的.map已经去除前后空格
                    if (apiKeyPattern.test(apiKey)) {
                        validApiKeys.push(apiKey); //成功的
                        //额外检测长度是否为51
                        if (!apiKeyPattern_51.test(apiKey)) {  //The length is not 51, the API Key may be incorrect.
                            validApiKeys_Not51.push(apiKey); //非51位，可能是错的。v7.19 目前OpenAI API Key为51位。
                        }
                    } else {
                        invalidApiKeys.push(apiKey); //失败的
                    }
                });

                //1) 手动点击提交按钮
                if (isManuallySubmit) {
                    //v7.20 新增方法，从json的文本中提取"标准格式"的key，做为补充、增强。 *只提取长度为51位的key
                    const regex_json = /"sk-[a-zA-Z0-9]{48}"/g; //正则： 含key双侧的双引号 + 固定51位标准格式
                    const matches_json = input.value.trim().match(regex_json);
                    if (matches_json != null && matches_json.length > 0) {
                        const result_json = matches_json.map(match => match.replace(/"/g, '')); //提取后，再去除双侧的双引号
                        validApiKeys = validApiKeys.concat(result_json);//追加到validApiKeys数组中
                    }
                }


                let isSaveApiKeys = false; //是否需要提交API Key

                if (validApiKeys.length === 0) { // 无API Key数据
                    notif = this.settingsLanguage == 'cn' ? '错误：未包含 API Key' : 'Error: No API Key included';
                } else if (invalidApiKeys.length === 0) { // 所有API Key都是有效的
                    isSaveApiKeys = true; //提交
                } else {
                    //1) 手动点击提交按钮
                    if (isManuallySubmit) {
                        notif = this.settingsLanguage == 'cn' ? '文本包含有非 API Key 的数据，已找到 ' + validApiKeys.length + ' 个 API Key，是否直接提交？' : 'The text contains non-API Key data. Currently, ' + validApiKeys.length + ' API Keys have been found. Would you like to submit them directly?';
                        this.is_Alert_Confirm_Prompt_Open = true;
                        if (confirm(notif)) { //弹窗询问是否继续提交
                            isSaveApiKeys = true; // 选择提交已识别到的API Key
                        } else {
                            // 选择取消
                            notif = this.settingsLanguage == 'cn' ? '未提交，请检查后重新提交' : 'Not submitted, please check and resubmit';
                        }
                        setTimeout(() => {
                            this.is_Alert_Confirm_Prompt_Open = false;
                        }, 100);
                    } else {
                        isSaveApiKeys = true; // 提交
                    }
                }

                let notif_type = 'bottom';
                let duplicate_found = false;

                //需要提交保存
                if (isSaveApiKeys) {
                    // 过滤重复值
                    const uniqueApiKeys = validApiKeys.filter((key, index) => {
                        return validApiKeys.indexOf(key) === index;//在上述代码中，我们使用filter()方法来过滤validApiKeys数组中的重复值，只保留第一次出现的值。
                    });
                    //标记批量添加中是否有重复项(有效 且重复的)
                    duplicate_found = uniqueApiKeys.length === validApiKeys.length ? false : true;
                    // 同理 过滤重复值
                    validApiKeys_Not51 = validApiKeys_Not51.filter((key, index) => {
                        return validApiKeys_Not51.indexOf(key) === index;
                    });

                    // 转换为 import_apicheckeddata_Array 数组
                    let import_apicheckeddata_Array = uniqueApiKeys.map((key) => {
                        return {apikey: key}; //最后，使用map()方法将uniqueApiKeys数组转换为import_apicheckeddata_Array数组，每个对象都包含apikey属性。
                    });

                    // 循环 import_apicheckeddata_Array 验证数据 & 兼容性处理
                    //判断属性是否存在且数据是否合法，如果缺少属性 或 属性数据不合法，就补上属性和默认值
                    this.validateApiCheckedData(import_apicheckeddata_Array);

                    // 合并更新数据到this.apiCheckedData
                    const newData = this.apiCheckedData.filter(apidata => apidata.apikey && apidata.apikey.trim() !== '');//排除空项

                    //数据统计
                    let newApiCount = 0;//不存在-新增
                    let existingApiCount = 0;//已存在
                    let newApiArray = [];//所有新增的key

                    //更新数据， 新增 或 更新
                    import_apicheckeddata_Array.forEach(apidata => {
                        const existingApiIndex = newData.findIndex(api => api.apikey === apidata.apikey);
                        if (existingApiIndex !== -1) {//存在 - 什么也不做
                            existingApiCount++;//存在 统计
                        } else {//不存在 - 新增
                            newData.push(apidata);
                            newApiArray.push(apidata.apikey);//记录新增
                            newApiCount++;//新增 统计
                        }
                    });
                    this.apiCheckedData = newData;

                    const apikeyData = this.getVariablesFromThis(this.apikeyData_default);
                    //手动增加版本
                    apikeyData.version = this.apikeyData_Version;
                    //保存
                    try {
                        localStorage.setItem('apikeyData', JSON.stringify(apikeyData));
                    } catch (e) {
                        const e_message = e.message ? e.message : '';
                        const e_name = e.name ? e.name : '';
                        this.localStorageExceedsMaximum(e_message, e_name);
                    }

                    if (!this.btnDisabledState_CheckAPI) { //如果当前是“未通过验证”的状态
                        //恢复apikey到输入框，并恢复按钮状态
                        this.restoreApiKeyInput(false, false);
                    }

                    let apikey_notif_cn = '';
                    let apikey_notif_en = '';
                    const duplicate_found_string = duplicate_found ? (this.settingsLanguage == 'cn' ? '去重后，' : 'After removing duplicates, ') : '';
                    //1) 手动点击提交按钮
                    if (isManuallySubmit) {
                        apikey_notif_cn = 'OK，批量添加完成。';
                        apikey_notif_en = 'OK, Bulk addition successful. '
                    } else {
                        apikey_notif_cn = 'OK，完成。';
                        apikey_notif_en = 'OK, Successful. '
                    }
                    apikey_notif_cn = apikey_notif_cn + '总共检测到 ' + import_apicheckeddata_Array.length + ' 个 API Key，其中：新的 ' + newApiCount + ' 个；已存在的 ' + existingApiCount + ' 个。';
                    apikey_notif_en = apikey_notif_en + 'Detected a total of ' + import_apicheckeddata_Array.length + ' API Keys: ' + newApiCount + ' new keys; ' + existingApiCount + ' existing keys.';

                    //新增的清单
                    if (newApiArray.length > 0) {
                        let newApi = this.settingsLanguage == 'cn' ? '\r\n\r\n以下为新增 API Key 的清单:' : '\r\n\r\nThe following are the newly added API Keys :';
                        newApi = newApi + "\r\n" + newApiArray.join("\r\n");
                        apikey_notif_cn = apikey_notif_cn + newApi;
                        apikey_notif_en = apikey_notif_en + newApi;
                    }

                    //非51位的清单
                    validApiKeys_Not51 = validApiKeys_Not51.filter(key => newApiArray.includes(key));//只保留newApiArray中存在的数据。 *validApiKeys_Not51之前没有排除"已存在的Key(this.apiCheckedData / newData 中存在的)"，借用newApiArray排除。
                    if (validApiKeys_Not51.length > 0) {
                        let not51 = this.settingsLanguage == 'cn' ? '\r\n\r\n其中部分 API Key 的位数不正确，疑似错误 Key 的清单 (是否有效以实际使用为准)：' : '\r\n\r\nSome of the API Keys have incorrect lengths (validity depends on actual usage) :';
                        not51 = not51 + "\r\n" + validApiKeys_Not51.join("\r\n");
                        apikey_notif_cn = apikey_notif_cn + not51;
                        apikey_notif_en = apikey_notif_en + not51;
                    }

                    notif = this.settingsLanguage == 'cn' ? apikey_notif_cn : apikey_notif_en;
                    notif_type = 'center';

                    //1) 手动点击提交按钮
                    if (isManuallySubmit) {
                        input.value = ''; //清空输入框
                        this.isShowBulkAddPanel_AutoKeyRotation = false;//批量添加结束，隐藏批量添加
                    }
                }

                //1) 手动点击提交按钮
                if (isManuallySubmit || isshownotif) {
                    //中心 或 底部
                    this.showNotification(notif, notif_type, '', 1800);
                }
            },

            // 自动轮换API Key窗口 Auto Key Rotation - 点击修改Level   Edit Level
            autoKeyRotation_EditLevel(apidata) {
                event.stopPropagation();
                //0 正忙，稍后再操作
                if (this.checkBusyStatus()) {
                    // 如果返回 true，正在发送或正在验证
                    return;
                }

                //保存 获取最新API Key数据
                this.saveApikeyData();

                const api_Data = this.apiCheckedData.find(data => data.apikey === apidata.apikey);
                if (api_Data == null) {
                    return;
                }

                // 显示弹窗，允许用户输入新的Level
                let newLevel = null;
                const regex = /^[01234589]?$/; // 匹配数字 0, 1, 2, 3, 4, 5 和 8, 9 的正则表达式
                const notif_cn = '所有[级别]都是全自动调节的，会自我修复，通常无需修改。\r\n如需修改请输入 1 位数字并确认：[0]匹配付费账号；[1～5]匹配非付费账号；[8]无效的密钥；[9]配额不足(含过期)。';
                const notif_en = '[Level] is intelligently adjusted and automatically repaired, usually requiring no modification.\r\nTo make changes, please enter a single digit and confirm: [0] for matching paid accounts; [1-5] for matching non-paid accounts; [8] for invalid API Key; [9] for insufficient quota (including expiration, etc.).';
                let text = this.settingsLanguage == 'cn' ? notif_cn : notif_en;
                let err = false;
                this.is_Alert_Confirm_Prompt_Open = true;
                while (newLevel === null || !regex.test(newLevel)) {
                    newLevel = newLevel === null ? (api_Data.level == null ? '' : api_Data.level) : newLevel;
                    if (err) {
                        text = this.settingsLanguage == 'cn' ? '所有[级别]都是全自动调节的，通常无需修改。\r\n如果不确定，可取消修改，或直接改为[0~5]的 1 位数字，值越小，优先级越高，会提前被调用并根据实际使用情况动态调节。' : '[Level] is intelligently adjusted and usually do not require modification.\r\nIf unsure, you can cancel the modification or directly change it to a single digit from [0-5]. The smaller the value, the higher the priority. It will be called in advance and dynamically adjusted based on actual usage.';
                    }
                    newLevel = window.prompt(text, newLevel);
                    if (newLevel === null) {
                        break; // 用户点击了取消按钮，结束循环
                    }
                    err = true;
                }
                setTimeout(() => {
                    this.is_Alert_Confirm_Prompt_Open = false;
                }, 100);

                if (newLevel === null) { //点击了取消
                    return;
                }
                if (newLevel === '') { //留空。其实留空可以将level设为null。但目前没有采用此逻辑，简单点，留空表示不变。
                    return;
                }
                newLevel = parseInt(newLevel); // 转为数值型并取整数
                if (newLevel < 0 || newLevel > 9 || newLevel === 6 || newLevel === 7) {
                    return; //不属于 0 1 2 3 4 5 和 8 9 ，返回
                }

                api_Data.level = newLevel;

                const apikeyData = this.getVariablesFromThis(this.apikeyData_default);
                //手动增加版本
                apikeyData.version = this.apikeyData_Version;
                try {
                    //保存到localStorage
                    localStorage.setItem('apikeyData', JSON.stringify(apikeyData));
                } catch (e) {
                    const e_message = e.message ? e.message : '';
                    const e_name = e.name ? e.name : '';
                    this.localStorageExceedsMaximum(e_message, e_name);
                }

                const notif = this.settingsLanguage == 'cn' ? '修改成功' : 'Modification successful';
                this.showNotification(notif, 'bottom');
            },

            // 自动轮换API Key窗口 Auto Key Rotation - Delete Key 点击删除API Key
            deleteApiData(apidata) {
                event.stopPropagation();
                if (this.checkBusyStatus()) {
                    // 如果返回 true，正在发送或正在验证
                    return;
                }

                //保存 获取最新API Key数据
                this.saveApikeyData();

                let notif = this.settingsLanguage == 'cn' ? '您确定要删除下面这个 API Key 吗？' : 'Are you sure you want to delete this API Key below?';
                notif = notif + '\r\n\r\n' + apidata.apikey;

                this.is_Alert_Confirm_Prompt_Open = true;
                const confirmed = confirm(notif); //用户选择是否继续，继续会删除
                if (!confirmed) {
                    setTimeout(() => {
                        this.is_Alert_Confirm_Prompt_Open = false;
                    }, 100);
                    return;//取消 将返回
                }
                setTimeout(() => {
                    this.is_Alert_Confirm_Prompt_Open = false;
                }, 100);

                const iscurrentkey = (this.api === apidata.apikey);

                this.apiCheckedData = this.apiCheckedData.filter(data => data.apikey !== apidata.apikey); //删除目标Key，保留其他
                notif = this.settingsLanguage == 'cn' ? 'API Key 已删除' : 'API Key has been deleted';
                this.showNotification(notif, 'bottom');


                const apikeyData = this.getVariablesFromThis(this.apikeyData_default);
                //手动增加版本
                apikeyData.version = this.apikeyData_Version;
                try {
                    //保存到localStorage
                    localStorage.setItem('apikeyData', JSON.stringify(apikeyData));
                } catch (e) {
                    const e_message = e.message ? e.message : '';
                    const e_name = e.name ? e.name : '';
                    this.localStorageExceedsMaximum(e_message, e_name);
                }


                //上次可用的，如果是本次被删的key，这里需要清除
                if (this.lastAPIKeyInputText_Available === apidata.apikey) {
                    this.lastAPIKeyInputText_Available = '';
                }
                //如果删除的是当前输入框内的key *这个代码必须放在最后，因为restoreApiKeyInput里可能会保存key，而保存方法不会同步删除，之后更新或新增。
                if (iscurrentkey) {
                    //恢复apikey到输入框，并恢复按钮状态
                    this.restoreApiKeyInput(false, false); //* 此代码必须放在最后，因为含有保存key的代码
                }

                //删完 无Key
                if (this.apiCheckedData.length < 1) {
                    if (this.btnDisabledState_CheckAPI) {
                        this.api = '';//清空输入框(最后一个Key)
                    }
                    this.changeButtonDisabledState(true);//改变按钮状态
                }
            },


            // 轮询 轮换 (Auto Key Rotation) 排序并返回第一个API Key对象。 返回的是apidata，不是直接返回apikey
            // excludeList（排除列表，非严格执行）：数组，支持两种类型：1.保持与this.autoRetry_apiKeyExcludeList相同的结构  2.字符串数组
            getFirstAvailableAPIKey(excludeList) {
                //对this.apiCheckedData进行排序，先按level排序升序，然后再对nexttime排序升序，最后取出第一个apikey数据
                //level数值型 级别，等级。可为null 目前有 0、1、和 2、3、4、5 和 8、9 。  0代表付费key，1代表每分钟保证有3～5次的独享的免费key，2、3、4、5代表不稳定的多人共用的免费key，8代表无效，9代表配额不足/过期的key
                //nexttime 预估的下次可用的时间 时间戳   不存在时=0

                //保存 获取最新API Key数据
                this.saveApikeyData();

                // 筛选出不在排除列表中的API Key对象
                let availableKeys;
                if (excludeList != null && excludeList[0] != null) {
                    if (typeof excludeList[0] === 'string') {
                        //普通字符串数组
                        availableKeys = this.apiCheckedData.filter(apidata => !excludeList.includes(apidata.apikey));
                    } else {
                        //this.autoRetry_apiKeyExcludeList的结构
                        availableKeys = this.apiCheckedData.filter(apidata => !excludeList.some(item => item.apikey === apidata.apikey));
                    }

                    const data_temp = availableKeys.filter(item => item.level !== 8 && item.level !== 9);
                    if (data_temp.length < 1) { //如果只剩下8 和 9，那么就不排除
                        availableKeys = this.apiCheckedData;
                    }
                } else {
                    availableKeys = this.apiCheckedData;
                }

                // 按级别和时间排序筛选后的API Key对象数组
                // 旧算法，备用方案，出错时才会用到。 备用方案不排除excludeList
                let sortedData = this.apiCheckedData.sort((a, b) => {
                    if (a.level !== b.level) {
                        return a.level - b.level;
                    } else {
                        return a.nexttime - b.nexttime;
                    }
                });
                // 过滤：保留全部level0 && 排除全部level8和level9 && 保留其他的nexttime预估时间已经到了的  *?? 保留level 0好像是多余的，不写也会保留。
                const sortedData_temp = sortedData.filter(item => item.level === 0 || (item.level !== 8 && item.level !== 9 && item.nexttime <= new Date().getTime()));
                if (sortedData_temp.length > 0) {
                    sortedData = sortedData_temp;
                }

                const sortedData_copy = sortedData.slice();//备份


                //???1111 可考虑增加排除条件：nexttime过滤大于1～7分钟（免费账号达到每日200次的上限）。好像也没必要排除。。。共享key，使用量大的可以考虑，过滤条件需要研究


                // 新的算法/策略
                try {
                    const randomNumber_1 = Math.random(); // 生成一个0到1之间的随机数
                    const randomNumber_2 = Math.random(); // 生成一个0到1之间的随机数
                    const randomNumber_3 = Math.random(); // 生成一个0到1之间的随机数

                    if (randomNumber_1 <= 0.8) { // A排序方案
                        // 80%采用A排序方案
                        sortedData = availableKeys.sort((a, b) => {
                            if (a.level !== b.level) {
                                return a.level - b.level; // 根据 level 进行升序排序
                            } else {
                                if (randomNumber_2 <= 0.65) {
                                    // 65%为第一种情况，nexttime 按升序排序
                                    return a.nexttime - b.nexttime;
                                } else {
                                    // 35%为第二种情况，过滤掉 nexttime 为 0 的项  *如果有批量添加的功能，nexttime初始为0，第二种情况：35%从已使用过的key中选取，避免每次都选新的key。
                                    const filteredData = availableKeys.filter(item => item.nexttime !== 0);
                                    if (filteredData.length === 0) {
                                        // 如果过滤后数量为0，则直接按 nexttime 升序排序
                                        return a.nexttime - b.nexttime;
                                    } else {
                                        // 过滤掉 nexttime 为 0 后的 availableKeys 按 nexttime 升序排序
                                        const sortedByNextTime = filteredData.sort((a, b) => a.nexttime - b.nexttime);
                                        return sortedByNextTime.indexOf(a) - sortedByNextTime.indexOf(b);
                                    }
                                }
                            }
                        });

                        const randomNumber_4 = Math.random(); // 生成一个0到1之间的随机数
                        if (randomNumber_4 <= 0.85) { //85%过滤。留15%机会在轮换中尝试更多的key
                            // 过滤：保留全部level0 && 排除全部level8和level9 && 保留其他的nexttime预估时间已经到了的  *?? 保留level 0好像是多余的，不写也会保留。
                            const temp_sortedData = sortedData.filter(item => item.level === 0 || (item.level !== 8 && item.level !== 9 && item.nexttime <= new Date().getTime()));
                            if (temp_sortedData.length > 0) {
                                sortedData = temp_sortedData;
                            }
                        }

                        //测试 console.log('A 80% ',sortedData);

                    } else { // B排序方案
                        // 20%采用B排序方案
                        let filteredData0 = availableKeys.slice(); // 复制一份原始数据

                        // 排除属性 level 为 8 和 9 的项 *先排除
                        const filteredData3 = filteredData0.filter(item => item.level !== 8 && item.level !== 9); //排除无效和 配额不足/过期的
                        if (filteredData3.length > 0) {
                            filteredData0 = filteredData3;
                        }

                        // 排除属性 nexttime 大于当前时间的项
                        const filteredData2 = filteredData0.filter(item => item.nexttime <= new Date().getTime()); //只保留nexttime已经到了预估时间的
                        if (filteredData2.length > 5) { //至少剩5个
                            filteredData0 = filteredData2;
                        }

                        // 排除属性 updatetime 为过去20秒以内的项
                        const filteredData1 = filteredData0.filter(item => item.updatetime < new Date().getTime() - 20000); //只保留更新时间为20秒之前的
                        if (filteredData1.length > 3) { //至少剩3个
                            filteredData0 = filteredData1;
                        }

                        // 排除属性 level 为 0 的项
                        const filteredData4 = filteredData0.filter(item => item.level !== 0); //排除付费的/无限使用的key，给机会尝试使用其他的key
                        if (filteredData4.length > 0) {
                            filteredData0 = filteredData4;
                        }

                        // 80%为方案一
                        if (randomNumber_3 <= 0.8) {
                            // 排除选 succrate 为 0 的项
                            const filteredData5 = filteredData0.filter(item => item.succrate !== 0);

                            if (filteredData5.length === 0) {
                                // 如果过滤后数量为0，则直接按 nexttime 进行升序排序
                                sortedData = filteredData0.sort((a, b) => a.nexttime - b.nexttime);
                            } else {
                                // 对属性 succrate 进行降序排序
                                const sortedBySuccrate = filteredData5.sort((a, b) => b.succrate - a.succrate);

                                // 取前5个（不够5个则保留全部）
                                const finalData = sortedBySuccrate.slice(0, 5); //成功率前5

                                // 按 nexttime 进行升序排序
                                sortedData = finalData.sort((a, b) => a.nexttime - b.nexttime);
                            }

                        } else {
                            // 20%为方案二
                            // 排除 succrate 不为 0 的项
                            const zeroSuccrateFilteredData = filteredData0.filter(item => item.succrate === 0); //尝试使用成功率为0的

                            // 先判断是否有为 0 的项
                            if (zeroSuccrateFilteredData.length > 0) {
                                // 如果有，则按照升序排序 succrate=0
                                sortedData = zeroSuccrateFilteredData.sort((a, b) => a.nexttime - b.nexttime);
                            } else {
                                // 如果没有，则按照降序排序
                                sortedData = filteredData0.sort((a, b) => a.nexttime - b.nexttime);
                            }

                        }

                        //测试 console.log('B 20% ',sortedData);
                    }
                } catch (e) {
                    console.error(e);
                    console.error('getFirstAvailableAPIKey() 获取key算法出错：', e.message);
                    //出错还原
                    sortedData = sortedData_copy;
                }

                // 返回排序后的第一个API Key对象。 *不是直接返回apikey
                return sortedData[0];
            },

            // (一个按钮两种状态/两种用途) Btn: window.sendRequest() or this.restoreApiKeyInput()
            sendRequest_or_restoreApiKeyInput() {
                if (this.btnDisabledState_CheckAPI) { //通过按钮状态判断
                    let apidata = this.apiCheckedData.find(data => data.apikey === this.api.trim());
                    let notif = '';
                    let notif_time = 4000;
                    let delaytime = 0;

                    if (apidata.level === 8) {
                        notif = this.settingsLanguage == 'cn' ? '当前查询的 API Key 无效，无法查询余额' : 'The current API Key being queried is invalid, and the balance inquiry cannot be completed.';
                        notif_time = this.settingsLanguage == 'cn' ? 4000 : 5500;
                        this.showNotification(notif, 'bottom', '', notif_time);
                        return;
                    }
                    if (apidata.level === null) {
                        notif = this.settingsLanguage == 'cn' ? '当前查询的 API Key 未曾验证过，如果该密钥无效，查询余额不会成功' : 'The current API Key being queried has not been checked. If the key is invalid, the balance inquiry will fail.';
                        notif_time = this.settingsLanguage == 'cn' ? 5000 : 6500;
                        delaytime = 800;
                        this.showNotification(notif, 'bottom', '', notif_time);
                    }
                    setTimeout(() => {
                        window.sendRequest(this.api.trim()); //余额查询 - 查询api key
                    }, delaytime);
                } else {
                    this.restoreApiKeyInput(false, true); //取消修改API Key，恢复API Key
                }
            },
            // 7月22日开始，OpenAI的规则改变，通过SessionKey查询余额
            // Starting from July 22nd, OpenAI's rules have changed. Balance inquiries are now required to be done using a Session Key.
            // 作废.....Session Key不是固定的，每次登录退出都会变化。
            // Invalid... The Session Key is not static and changes with each login and logout.
            sendRequest_usingSessionKey() {

                let sess_key = null;
                const regex = /^sess-[a-zA-Z0-9]{40}$/; // 正则表达式 sess- 开头 + 40位大小写英文和数字的组合
                const notif_cn = ' • 7月22日起，OpenAI 已停止通过 API Key 查询余额 (余额不显示)，现在查询余额需要使用 Session Key 。\r\n\r\n • 获取方法(具体请网络搜索)：登录 OpenAI 后，通过浏览器的开发者工具 ，在 [Headers/标头] 中找到 [Authorization]，可获得以“sess-”开头的总长度为45位的 Session Key。\r\n\r\n请将其粘贴在下方，查询将返回包含余额在内的完整的信息：';
                const notif_en = ' • Starting from July 22nd, OpenAI has stopped displaying the balance when querying with an API Key. Now, to check the balance, you need to use a Session Key. \r\n\r\n • To obtain the Session Key (for specifics, please Google): After logging in to OpenAI, open DevTools in your browser and look for [Authorization] under [Headers]. There, you will find a 45-character Session Key starting with "sess-". \r\n\r\nPlease paste it here to check the balance:';
                let text = this.settingsLanguage == 'cn' ? notif_cn : notif_en;
                let err = false;
                this.is_Alert_Confirm_Prompt_Open = true;
                while (sess_key === null || !regex.test(sess_key.trim())) {
                    sess_key = sess_key === null ? '' : sess_key.trim();
                    if (err) {
                        text = this.settingsLanguage == 'cn' ? 'Session Key 格式错误！\r\n\r\nsess码是以“sess-”开头的总长度为45位的密钥，请将其粘贴在下方，确认查询后将返回 OpenAI API 余额信息：' : 'Invalid Session Key format! \r\n\r\nThe key should start with "sess-" and have a total length of 45 characters. Please paste it below and confirm. After the query, it will return the OpenAI API balance:';
                    }
                    sess_key = window.prompt(text, sess_key);
                    if (sess_key === null) {
                        break; // 用户点击了取消按钮，结束循环
                    }
                    err = true;
                }
                setTimeout(() => {
                    this.is_Alert_Confirm_Prompt_Open = false;
                }, 100);

                if (sess_key === null) { //点击了取消
                    return;
                }

                window.sendRequest(sess_key.trim()); //余额查询 - 查询Session Key

            },
            // 恢复APIkey输入框的内容  isconfirm 是否需要弹窗询问
            restoreApiKeyInput(isconfirm, isshownotif) {
                if (this.checkBusyStatus()) {
                    // 如果返回 true，正在发送或正在验证
                    return;
                }

                isconfirm = isconfirm === true ? true : false;
                isshownotif = isshownotif === true ? true : false;
                isshownotif = isconfirm ? true : isshownotif; //弹窗询问时，一定需要通知
                let notif;

                setTimeout(() => {

                    if (this.lastAPIKeyInputText_Available === '' && this.apiCheckedData.length < 1 && this.defaultAPIKey === '' && this.default_apiKeyArray_WrittenInTheCode.length === 0) {
                        if (!isconfirm && isshownotif) { //当前为“点击按钮”恢复 && 允许通知
                            this.api = '';
                            notif = this.settingsLanguage == 'cn' ? '无可恢复的 API Key，请继续输入新的Key并验证' : 'No available API Key to restore. Please enter a new Key and check it.';
                            this.showNotification(notif, 'bottom', '', 4500);
                        }
                        return;
                    }

                    if (isconfirm) { //当前为弹窗询问
                        notif = this.settingsLanguage == 'cn' ? '是否需要恢复API Key？' : 'Do you want to restore the API Key?';
                        this.is_Alert_Confirm_Prompt_Open = true;
                        if (!confirm(notif)) {//弹窗询问
                            setTimeout(() => {
                                this.is_Alert_Confirm_Prompt_Open = false;
                            }, 100);
                            return;//点击取消
                        }
                        setTimeout(() => {
                            this.is_Alert_Confirm_Prompt_Open = false;
                        }, 100);
                    }
                    if (isshownotif) {
                        notif = this.settingsLanguage == 'cn' ? '已恢复 API Key' : 'API Key has been restored';
                        this.showNotification(notif, 'bottom', '', 2000);
                    }

                    //上次的有效的key
                    if (this.lastAPIKeyInputText_Available !== '') {
                        this.api = this.lastAPIKeyInputText_Available;
                        this.inputapichange(false);
                        return;
                    }
                    //上次输入框中的key
                    //if (this.lastAPIKeyInputText !== ''){
                    //	if(this.apiCheckedData.some(item => item.apikey === this.lastAPIKeyInputText)){
                    //		this.api = this.lastAPIKeyInputText;
                    //		this.inputapichange(false);
                    //		return;
                    //	}
                    //}

                    //写在代码中的key数组 "default_apiKeyArray_WrittenInTheCode"
                    if (this.default_apiKeyArray_WrittenInTheCode.length > 0) {
                        const foundElement = this.default_apiKeyArray_WrittenInTheCode.find(key => {
                            return this.apiCheckedData.some(obj => obj.apikey === key);
                        });//找出已验证过的
                        if (foundElement != null) { //如果存在
                            this.api = foundElement;
                            this.inputapichange(false);
                            return;
                        }
                    }
                    //写在代码中的单个key "api"
                    if (this.defaultAPIKey !== '') {
                        if (this.apiCheckedData.some(item => item.apikey === this.defaultAPIKey)) {
                            this.api = this.defaultAPIKey;
                            this.inputapichange(false);
                            return;
                        }
                    }
                    //已验证key的数组中取key
                    const getkey = this.getFirstAvailableAPIKey();
                    if (getkey != null && getkey.apikey != null) {
                        this.api = getkey.apikey;
                        this.inputapichange(false);
                        return;
                    }

                    //以下：其他情况，未验证的key
                    let unverified_key = '';
                    if (this.default_apiKeyArray_WrittenInTheCode.length > 0) {
                        unverified_key = this.default_apiKeyArray_WrittenInTheCode[0];
                    }
                    unverified_key = unverified_key !== '' ? unverified_key : (this.defaultAPIKey !== '' ? this.defaultAPIKey : this.lastAPIKeyInputText);
                    this.api = unverified_key;
                    this.inputapichange(false);
                }, 100);
            },

            // 更新this.apiCheckedData数组（已验证的API Key信息），更新key数据的策略(Algorithm/Strategy for updating API key data)  - 轮询 / 自动轮换API Key / Auto Key Rotation
            updateApiCheckedData(api_key, requesttime, state) {
                if (api_key == null || api_key === '' || requesttime == null || requesttime === 0 || state == null) {
                    console.error("updateApiCheckedData - Err - Null");
                    return;
                }
                if (requesttime.toString().length !== 13 || isNaN(new Date(requesttime).getTime())) {
                    console.error("updateApiCheckedData - Err - not Timestamp");
                    return;
                }
                if (state === '') {
                    return;
                }
                api_key = api_key.trim();

                //保存 获取最新API Key数据
                this.saveApikeyData();

                let apidata = this.apiCheckedData.find(data => data.apikey === api_key);

                try {//测试
                    //	console.log('updateApiCheckedData 前 - apidata ',apidata);
                    //	console.log('updateApiCheckedData 前 - apidata date1 ',new Date(apidata.date1).toLocaleString());
                    //	console.log('updateApiCheckedData 前 - apidata date2 ',new Date(apidata.date2).toLocaleString());
                    //	console.log('updateApiCheckedData 前 - apidata date3 ',new Date(apidata.date3).toLocaleString());
                    //	console.log('updateApiCheckedData 前 - apidata nexttime ',new Date(apidata.nexttime).toLocaleString());
                    //	console.log('updateApiCheckedData 前 - apidata level ',apidata.level);
                } catch (e) {
                }

                // start - 已经存在的key
                if (apidata) {

                    //先更新最后操作时间
                    apidata.updatetime = new Date().getTime();


                    if (state === 'success') {
                        //发送成功
                        //level: 0  1  2 3 4 5

                        let isLevelChanged = false;
                        // 1) Level 第1次更新(更新时间数据之前判断，apidata.date123的时间可能远超1分钟)
                        if (apidata.level >= 0 && apidata.level <= 5) { // 0  1  2 3 4 5
                            if (apidata.date3 !== 0) { //即当前存在4个时间数据供分析判断：3个时间记录 + 最新时间requesttime
                                const date1_4 = requesttime - apidata.date1; //最新成功的时间 - 第一个成功的时间
                                if (date1_4 < 19500) { // 防止误差，20秒减0.5秒
                                    //19.5秒内成功发送4次，超出免费账户的范围，自动标为付费账户
                                    apidata.level = 0;
                                    isLevelChanged = true;
                                } else if (date1_4 < 39500) {
                                    //39.5秒内成功发送4次，免费账户 或者 付费账户  *截止2023.7.14，OpenAI规则，免费key超过1分钟未使用时，20秒内最多发3次，然后限制20秒发一条，即免费账号40秒最高记录可发4条
                                    apidata.level = apidata.level === 0 ? 0 : 1; //保持0 或者保持1 或者改为1
                                    isLevelChanged = true;
                                } else if (date1_4 < 59500) {
                                    apidata.level = (apidata.level - 3) >= 1 ? (apidata.level - 3) : 1; //提升3级
                                    isLevelChanged = true;
                                }
                            }
                        }

                        // 2) 统计
                        apidata.succcount++; //成功次数+1
                        //apidata.succrate = parseFloat((apidata.succcount / ).toFixed(4)); // 成功率
                        apidata.succrate = Math.round((apidata.succcount / (apidata.succcount + apidata.failcount)) * 10000) / 10000; // 成功率
                        //为了保证成功率的准确度和实效性，达到条件就压缩次数，缩小5倍，并向上取整数
                        if (apidata.succcount > 100 || apidata.failcount > 100) {
                            apidata.succcount = Math.ceil(apidata.succcount / 5);
                            apidata.failcount = Math.ceil(apidata.failcount / 5);
                        }

                        // 3) date1 &2 &3
                        // 检查是否超过60.5秒，超过60.5秒设为0
                        if (requesttime - apidata.date1 > 60500) {
                            apidata.date1 = 0;
                        }
                        if (requesttime - apidata.date2 > 60500) {
                            apidata.date2 = 0;
                        }
                        if (requesttime - apidata.date3 > 60500) {
                            apidata.date3 = 0;
                        }

                        //移位，重新排序
                        if (apidata.date1 === 0) {
                            apidata.date1 = apidata.date2;
                            apidata.date2 = apidata.date3;
                            apidata.date3 = 0;
                        }
                        if (apidata.date1 === 0) {
                            apidata.date1 = apidata.date2;
                            apidata.date2 = 0;
                        }

                        //写入本次的请求时间 requesttime
                        if (apidata.date1 === 0) {
                            apidata.date1 = requesttime;
                        } else if (apidata.date2 === 0) {
                            apidata.date2 = requesttime;
                        } else if (apidata.date3 === 0) {
                            apidata.date3 = requesttime;
                        } else {
                            apidata.date1 = apidata.date2;
                            apidata.date2 = apidata.date3;
                            apidata.date3 = requesttime;
                        }

                        // 4) nexttime
                        if (apidata.date2 === 0 || apidata.date3 === 0) { // date2或date3为0，可继续尝试 *当前1分钟内连续成功1～2次
                            apidata.nexttime = requesttime; //设为请求时的时间，优先级较高
                        } else if ((apidata.date3 > (apidata.date2 + 20500)) || (apidata.date3 > (apidata.date1 + 20500))) { //超过20.5秒，可继续发送。 *当前1分钟内连续成功3次，且有较大概率能发第4条
                            apidata.nexttime = requesttime;  //设为请求时的时间，优先级较高
                        } else { // +20.5秒
                            apidata.nexttime = requesttime + 20500; //其他情况，预估20.5秒后可用
                        }

                        // 1) Level 第2次更新(更新时间数据之后判断，apidata.date123的时间是1分钟以内的)
                        if (apidata.level === 8 || apidata.level === 9 || apidata.level === null) {
                            // 8：无效 疑似无效  9：曾经配额不足/过期（现在可能恢复配额了） null：首次标注level
                            apidata.level = 1; //先标为1，先升后降
                        } else if (isLevelChanged === false && apidata.level >= 0 && apidata.level <= 5) { // 012345
                            if (apidata.date3 !== 0) { //1分钟内3次
                                apidata.level = (apidata.level - 3) >= 1 ? (apidata.level - 3) : (apidata.level < 1 ? apidata.level : 1); //提升3级，最高到1级（有较高概率是1级，先提升），0级则保持不变
                            } else if (apidata.date2 !== 0) { //1分钟内2次
                                apidata.level = (apidata.level - 2) >= 2 ? (apidata.level - 2) : (apidata.level < 2 ? apidata.level : 2); //如果大于2，提升2级，最高到2级（无法证明是1级）
                            } else { //1分钟内1次
                                apidata.level = (apidata.level - 1) >= 2 ? (apidata.level - 1) : (apidata.level < 2 ? apidata.level : 2); //如果大于2，提升1级，最高到2级（无法证明是1级）
                            }
                        }

                        // 5) apiurl *最后成功的apiurl  暂时没什么用，先留着
                        apidata.apiurl = this.apiURL.trim();

                    } else if (state === '429-2' || state === '404') {
                        //发送失败 无额度 || 账号无组织机构
                        //level: 9

                        // 1) Level
                        apidata.level = 9;

                        // 2) 统计
                        apidata.failcount++; //失败次数+1
                        apidata.succrate = Math.round((apidata.succcount / (apidata.succcount + apidata.failcount)) * 10000) / 10000; // 成功率
                        //为了保证成功率的准确度和实效性，达到条件就压缩次数，缩小5倍，并向上取整数
                        if (apidata.succcount > 100 || apidata.failcount > 100) {
                            apidata.succcount = Math.ceil(apidata.succcount / 5);
                            apidata.failcount = Math.ceil(apidata.failcount / 5);
                        }

                        // 4) nexttime
                        apidata.nexttime = requesttime; //对于level = 9 实际作用是记录失败时间

                    } else if (state === '429-3' || state === '429-4' || state === '500' || state === '503') {
                        //（不占用请求额度）可立即重试
                        //level: 0 1 2345 8

                        // 1) Level
                        if (apidata.level === 9) {
                            apidata.level = null; //设为null
                        }

                    } else if (state === '429-1' || state === '429-1-day' || state === '429-5' || state === '400') {
                        //（不占用请求额度）请求次数过快 || （不占用请求额度）免费账号达到每天的请求上限 ||（不占用请求额度）请求过快(tokens每分钟) || （占用请求额度）tokens超出
                        //level: 0 1 2345

                        // 1) Level
                        if (state === '429-1' || state === '429-1-day') {
                            if (apidata.level === 8 || apidata.level === 9) {
                                apidata.level = null; //设为null
                            }
                            if (apidata.level === 0) {
                                apidata.level = 1; //无限账户 降为免费账户
                            } else if (apidata.level >= 1 && apidata.level <= 5) { //以下判断条件比较宽松，不严谨，大多数情况降级处理，有利于自动轮换尝试不同的key
                                if (apidata.date1 === 0) { //0个时间记录
                                    apidata.level = (apidata.level + 2) <= 5 ? (apidata.level + 2) : 5;
                                } else if (apidata.date2 === 0) { //1个时间记录
                                    if ((requesttime - apidata.date1) > 20500) {
                                        apidata.level = (apidata.level + 1) <= 5 ? (apidata.level + 1) : 5;
                                    }
                                } else if (apidata.date3 === 0) { //2个时间记录
                                    if ((requesttime - apidata.date1) > 40500 || (requesttime - apidata.date2) > 20500) {
                                        apidata.level = (apidata.level + 1) <= 5 ? (apidata.level + 1) : 5;
                                    }
                                } else {
                                    apidata.level = (apidata.level + 1) <= 5 ? (apidata.level + 1) : 5;
                                }
                            }
                        } else if (state === '400') { //算成功
                            if (apidata.level === 8 || apidata.level === 9 || apidata.level === null) {
                                apidata.level = 1; //先标为1，先升后降
                            } else if (apidata.level >= 0 && apidata.level <= 5) { // 0  1  2 3 4 5
                                if (apidata.date3 !== 0) { //即当前存在4个时间数据供分析判断：3个时间记录 + 最新时间requesttime
                                    const date1_4 = requesttime - apidata.date1; //最新成功的时间 - 第一个成功的时间
                                    if (date1_4 < 19500) { // 防止误差，20秒减0.5秒
                                        //19.5秒内成功发送4次，超出免费账户的范围，自动标为付费账户
                                        apidata.level = 0;
                                        isLevelChanged = true;
                                    } else if (date1_4 < 39500) { //39.5秒
                                        //39.5秒内成功发送4次，免费账户 或者 付费账户  *截止2023.7.14，OpenAI规则，免费key超过1分钟未使用时，20秒内最多发3次，然后限制20秒发一条，即免费账号40秒最高记录可发4条
                                        apidata.level = apidata.level === 0 ? 0 : 1; //保持0 或者保持1 或者改为1
                                        isLevelChanged = true;
                                    } else if (date1_4 < 59500) { //59.5秒
                                        apidata.level = (apidata.level - 3) >= 1 ? (apidata.level - 3) : 1; //提升3级
                                        isLevelChanged = true;
                                    } else {
                                        apidata.level = (apidata.level - 1) >= 2 ? (apidata.level - 1) : 2; //简单处理，先提升1级
                                    }
                                } else {
                                    apidata.level = (apidata.level - 1) >= 2 ? (apidata.level - 1) : 2; //简单处理，先提升1级
                                }
                            }
                        } else if (state === '429-5') {  //tokens请求过快(每分钟)
                            if (apidata.level === 8 || apidata.level === 9) {
                                apidata.level = null; //设为null
                            }
                            //其他情况，保持不变
                        }

                        // 2) 统计
                        if (state === '429-1' || state === '429-1-day' || state === '429-5') { //失败，
                            apidata.failcount++; //失败次数+1
                            apidata.succrate = Math.round((apidata.succcount / (apidata.succcount + apidata.failcount)) * 10000) / 10000; // 成功率
                        } else if (state === '400') { //标记为成功，表示有机会。 *另外，发生400错误的次数较少，标记为成功影响不大。
                            apidata.succcount++; //成功次数+1
                            apidata.succrate = Math.round((apidata.succcount / (apidata.succcount + apidata.failcount)) * 10000) / 10000; // 成功率
                        }
                        //为了保证成功率的准确度和实效性，达到条件就压缩次数，缩小5倍，并向上取整数
                        if (apidata.succcount > 100 || apidata.failcount > 100) {
                            apidata.succcount = Math.ceil(apidata.succcount / 5);
                            apidata.failcount = Math.ceil(apidata.failcount / 5);
                        }

                        // 4) nexttime  *统一加20.5秒，之前分多个档会产生很多问题
                        if (state === '429-1-day') { //429-1-day: 免费账号达到每天的上限后，每7分12秒获得1次请求机会。不具体分析，只按7分12秒加
                            apidata.nexttime = requesttime + 432000; //7m12s = 432s  432秒=432000毫秒
                        } else if (state === '429-5') { //tokens请求过快(每分钟)
                            apidata.nexttime = requesttime;
                        } else {
                            if (apidata.date1 === 0) { //0个时间记录
                                apidata.nexttime = requesttime + 20500;
                            } else if (apidata.date2 === 0) { //1个时间记录
                                if (state === '429-1') {
                                    apidata.nexttime = apidata.date1 + 20500; //不占用请求额度，从date1开始计算
                                    apidata.nexttime = apidata.nexttime < requesttime ? requesttime : apidata.nexttime; //原因：多延点时间，同级别的其他key才会有机会轮换到
                                } else {
                                    apidata.nexttime = requesttime + 20500;
                                }
                            } else if (apidata.date3 === 0) { //2个时间记录
                                if (state === '429-1') {
                                    apidata.nexttime = apidata.date2 + 20500;
                                    apidata.nexttime = apidata.nexttime < requesttime ? requesttime : apidata.nexttime;
                                } else {
                                    apidata.nexttime = requesttime + 20500;
                                }
                            } else {
                                apidata.nexttime = requesttime + 20500;
                            }
                        }

                        // 5) apiurl *最后成功的apiurl  暂时没什么用，先留着
                        if (state === '400') { //算成功
                            apidata.apiurl = this.apiURL.trim();
                        }

                    } else if (state === '429-other' || state === 'error-other') {
                        //其他情况 +20.5s
                        //level: 保持不变

                        // 1) Level
                        if (apidata.level >= 1 && apidata.level <= 5) {
                            apidata.level = (apidata.level + 1) <= 5 ? (apidata.level + 1) : 5;
                        }

                        // 2) 统计
                        apidata.failcount++; //失败次数+1
                        apidata.succrate = Math.round((apidata.succcount / (apidata.succcount + apidata.failcount)) * 10000) / 10000; // 成功率

                        // 4) nexttime
                        apidata.nexttime = requesttime + 20500;
                    } else if (state === '401') {
                        // 无效的key，401
                        // Key是随机生成的，401 无效的key正常情况下不可能会恢复可用
                        // level: 8

                        // 1) Level
                        apidata.level = 8;

                        // 2) 统计
                        apidata.failcount++; //失败次数+1
                        apidata.succrate = Math.round((apidata.succcount / (apidata.succcount + apidata.failcount)) * 10000) / 10000; // 成功率

                        // 4) nexttime
                        apidata.nexttime = requesttime; //对于level = 8 实际作用是记录失败时间
                    }
                    // end - 已经存在的key

                } else {
                    // start - 不存在的key 验证/添加新Key时

                    let level = null;
                    let succcount = 0;
                    let failcount = 0;
                    let succrate = 0;
                    let date1 = 0;
                    let nexttime = 0;
                    let apiurl = '';

                    if (state === 'success') {  //不存在的key 正常不会出现发送成功情况
                        //发送成功
                        //level: 0 1 2345

                        // 1) Level
                        level = 1; //先标为1

                        // 2) 统计
                        succcount = 1; //成功次数
                        succrate = Math.round((succcount / (succcount + failcount)) * 10000) / 10000; // 成功率:1

                        // 3) date1 &2 &3
                        date1 = requesttime;

                        // 4) nexttime
                        nexttime = requesttime;

                        // 5) apiurl *最后成功的apiurl  暂时没什么用，先留着
                        apiurl = this.apiURL.trim();

                    } else if (state === '429-2' || state === '404') {
                        //发送失败 无额度 || 账号无组织机构
                        //level: 9
                        // 1) Level
                        level = 9;

                        // 2) 统计
                        failcount = 1; //失败次数
                        succrate = Math.round((succcount / (succcount + failcount)) * 10000) / 10000; // 成功率:0

                        // 3) date1 &2 &3  默认0

                        // 4) nexttime
                        nexttime = requesttime; //对于level = 9 实际作用是记录失败时间

                        // 5) apiurl 默认''

                    } else if (state === '429-3' || state === '429-4' || state === '500' || state === '503') {
                        //（不占用请求额度）可立即重试
                        //level: 0 1 2345 8

                        // 1) Level 默认null  高优先级
                        // 2) 统计 默认0
                        // 3) date1 &2 &3 默认0
                        // 4) nexttime 默认0  高优先级
                        // 5) apiurl 默认''

                    } else if (state === '429-1' || state === '429-1-day' || state === '400') {
                        //（不占用请求额度）请求过快  || （不占用请求额度）免费账号达到每天的请求上限 || （占用请求额度）tokens超出
                        //level: 0 1 2345

                        // 1) Level
                        level = 1; //先标为1

                        // 2) 统计
                        if (state === '429-1' || state === '429-1-day') {
                            failcount = 1; //失败次数
                            succrate = Math.round((succcount / (succcount + failcount)) * 10000) / 10000; // 成功率:0
                        } else if (state === '400') {
                            succcount = 1; //成功次数
                            succrate = Math.round((succcount / (succcount + failcount)) * 10000) / 10000; // 成功率:1
                        }

                        // 3) date1 &2 &3 默认0

                        // 4) nexttime
                        if (state === '429-1-day') { //429-1-day: 免费账号达到每天的上限后，每7分12秒获得1次请求机会。不具体分析，只按7分12秒加
                            nexttime = requesttime + 432000; //7m12s = 432s  432秒=432000毫秒
                        } else {
                            nexttime = state === '400' ? requesttime : requesttime + 20500;
                        }

                        // 5) apiurl
                        if (state === '400') { //算成功
                            apiurl = this.apiURL.trim();
                        }

                    } else if (state === '429-other' || state === 'error-other') {
                        //+20s
                        //level: 保持不变
                        //**此处可能考虑排除'error-other'，原因：验证新的key时，出错，错误代码'error-other'可能包含不应该新增key的情况。当然，新增了问题也不大，另外可以手动删除。

                        // 1) Level 默认null  高优先级

                        // 2) 统计
                        failcount = 1; //失败次数
                        succrate = Math.round((succcount / (succcount + failcount)) * 10000) / 10000; // 成功率:0

                        // 3) date1 &2 &3 默认0

                        // 4) nexttime
                        nexttime = requesttime + 20500;

                        // 5) apiurl 默认''
                    } else if (state === '401') {
                        // 无效的key，401
                        // Key是随机生成的，401 无效的key正常情况下不可能会恢复可用
                        // level: 新增不添加401的Key
                        // 不添加
                    }

                    // 401 一定要排除，不添加。
                    // 备忘录：429-2 不能排除，429-2 是可以续费的，不一定是免费账号，另外，catchApiErrorCode中 429-2 会提醒用户已保存Key，但是超配额了。
                    // v7.20 增加排除2种情况:'404'、'error-other'。前面的代码没有排除(代码保留)，仅在这里排除。如果不需要排除，删除判断即可。 *不排除也行，不影响实际使用
                    if (state !== '401' && state !== '404' && state !== 'error-other') {
                        //4种不需要添加API Key的情况：无效的Key && 无额度 && 账号无组织机构 && 未知类型的错误

                        this.apiCheckedData.push({
                            "apikey": api_key,
                            "apiurl": apiurl,
                            "date1": date1,
                            "date2": 0,
                            "date3": 0,
                            "nexttime": nexttime,
                            "level": level,
                            "freq": 0,
                            "vpn": '',
                            "owner": 'me',
                            "succcount": succcount,
                            "failcount": failcount,
                            "succrate": succrate,
                            "updatetime": new Date().getTime()
                        })
                    }

                }
                // end - 不存在的key


                try {//测试
                    //	console.log('updateApiCheckedData 结尾  - this.apiCheckedData ',this.apiCheckedData);
                    //	console.log('updateApiCheckedData 结尾  - apidata ',apidata);
                    //	console.log('updateApiCheckedData 结尾  - apidata date1 ',new Date(apidata.date1).toLocaleString());
                    //	console.log('updateApiCheckedData 结尾  - apidata date2 ',new Date(apidata.date2).toLocaleString());
                    //	console.log('updateApiCheckedData 结尾  - apidata date3 ',new Date(apidata.date3).toLocaleString());
                    //	console.log('updateApiCheckedData 结尾  - apidata nexttime ',new Date(apidata.nexttime).toLocaleString());
                    //	console.log('updateApiCheckedData 结尾  - apidata level ',apidata.level);
                } catch (e) {
                }


                //再次保存 获取最新API Key数据
                this.saveApikeyData();
            },


            //按钮：清空 清空记忆 Clear context *同时清空发送失败的msg
            clearContext() {
                this.btnDisabledState_Clear = true;
                this.btnDisabledState_Undo = true;
                this.totaltokens = 0; //int
                this.msgContent = [this.msgContent[0]];
                this.msgTokens = [this.msgTokens[0]];
                this.succQA_Count = 0; //int
                this.msgContentForMsgList_SingleRound = [this.msgContentForMsgList_SingleRound[0]];//清零，仅记录单轮记忆的原文
                this.userMsgTokensForRetry = 0; //清零
                const txt_lang1 = this.settingsLanguage == 'cn' ? '### #清空上下文记忆\r\n\r\n' : '### #Clear Context\r\n\r\n';
                this.chathistory += (txt_lang1);
                this.add_hr_newRoundContext(false); //添加记忆分割线

                //更新 保存 聊天数据 chatData chatDataList
                //更新最后活动时间
                this.updateChatLastTime(this.formatDateYYYYMMDDHHmmss(new Date(), ':'));//最后活动时间
            },
            //按钮：说明 Help
            openDialog_Help() {
                let notif = '';
                let title = this.settingsLanguage == 'cn' ? 'LinGPT v7.29 - 帮助' : 'LinGPT v7.29 - Help';
                let notifcn = 'LinGPT - A GPT-3.5 Webpage with Just a Single HTML File ';
                notifcn += '\r\n\r\nLinGPT ，只有一个 html 文件的 GPT-3.5 聊天网页，轻量，便捷，零门槛。下载到电脑，打开 html 文件即可使用，像打开一张图片一样简单。可上传至托管平台 / 主机 / 服务器，支持所有免费的静态网页托管平台，托管在 Github / Gitee 非常流畅。 1) 接口: 使用 OpenAI 官方 API ，同时可自定义 API ，支持免魔法的第三方 API 。 2) 模型: 目前没有可以测试 GPT-4 模型的账号，所以仅支持 GPT-3.5 模型 (已升级支持最新的 ChatGPT 16K 版本) 。 3) 密钥: 需自备 API Key，不限数量，支持轮询，全自动智能的轮换 API Key，无需设置。 4) 数据安全: 支持浏览器本地存储，所有数据仅存储在浏览器缓存中，支持导出、导入、删除。代码未加密，开源透明，安全无风险，推荐搭配官方接口使用。\r\n\r\n#开始使用\r\n跟随红色按钮指引，填入 API Key 或批量添加后即可开始聊天。';
                notifcn += '\r\n\r\n#对话\r\n功能: 多对话管理、全自动保存、支持创建副本、多标签页数据同步、可导出 (备份/迁移/分享)、可导入 (还原对话)。 \r\n聊天: 1) 全自动智能调节 max_tokens ，告别手动设置。 2) 支持自动重问 (当发送失败时，智能判断是否重试)。 3) 可使用 Markdown 语法，方法与官网版 ChatGPT (chat.openai.com) 相同，当 GPT 回复未使用 Markdown 格式时，可要求其使用 Markdown 格式回复（简称 MD 格式，主要应用于代码、表格、文章排版、显示网络图片等，多数情况下 GPT 会主动使用 MD 语法）。 4) iPhone / iPad 中，搜狗输入法在换行时可能存在兼容性问题，。 5) 电脑端支持 Shift + Enter 、Ctrl + Enter 、Command(⌘) + Enter 换行。 6) 如果想中断正在发送的请求，可通过刷新网页来实现。';
                notifcn += '\r\n\r\n#提示词窗口\r\n截止 v7.29 版本，暂时只有设置系统提示词的功能，可在提示词窗口中点击蓝色小问号 [?] 了解更多。\r\n\r\n#设置窗口\r\n常规设置、 API 设置、查询 API 余额和有效期 (余额查询于7月22日失效)、 API 专业设置、上传头像、导出纯文本聊天记录、本地数据管理等，可在设置窗口中点击蓝色小问号 [?] 了解更多。\r\n\r\n#持续更新中\r\n更新日志 https://gitee.com/GiteeKey/ruoyi-blog/  或  https://github.com/lin2025/gpt3.5/ \r\n待开发的功能：集成提示词、共享提示词、压缩上下文(可导出)、多种上下文模式...等';
                //有单引号
                let notifen = "LinGPT - A GPT-3.5 Webpage with Just a Single HTML File \r\n\r\nLinGPT is a lightweight single-page chat application that can be deployed statically, with no need for a server. It can be uploaded to any static website hosting platform like GitHub and provides a very smooth browsing experience.  1) API: This code uses the OpenAI API and supports third-party APIs. 2) Model: Currently, only the GPT-3.5 model (Supports the latest ChatGPT 16K) is supported as there is no account available for testing the GPT-4 model. 3) API Key: OpenAI API key required, no limit on quantity, supports Auto Key Rotation, intelligently rotates API keys. 4) User Data Security: Supports browser local storage. All data is stored solely in the browser cache and supports export, import, and deletion. ";
                notifen += "\r\n\r\n#Getting Started \r\nFollow the red button's guidance, enter your API Key or add them in bulk, and start chatting. ";
                notifen += '\r\n\r\n#Chat\r\nFeatures: Multiple chat management, automatic saving, support for creating copies, data synchronization across multiple tab pages, export capability (backup/migration/sharing), import capability (restore chats). \r\nChatting: 1) Fully automatic intelligent adjustment of max_tokens. 2) Supports automatic retry (intelligently determines whether to retry when a request fails). 3) Markdown syntax can be used, same as the official ChatGPT (chat.openai.com). When GPT replies without using Markdown syntax, it can be asked to reply in Markdown format (referred to as MD, mainly used for code, tables, article formatting, displaying web images, etc. In most cases, GPT will automatically use MD syntax). 4) Line Breaks on Computer: Supports Shift + Enter, Ctrl + Enter line breaks, and Mac also support Command(⌘) + Enter line breaks. 5) There is no stop request function. If you want to interrupt a request that is being sent, you can do so by refreshing the web page.';
                notifen += "\r\n\r\n#Prompt Generator \r\nv7.29, only the function of setting system prompt is available. Click the blue question mark [?] in the prompt generator window to learn more. \r\n\r\n#Settings \r\nFeatures: General settings, API settings, checking API balance and validity period (The balance inquiry expired on July 22nd), API professional settings, uploading avatars, exporting chat history as plain text, local data management, etc. Learn more by clicking the blue question mark [?] in the settings window. \r\n\r\n#Updates \r\nhttps://github.com/lin2025/gpt3.5/ \r\nFeatures to be developed: Integrated prompts, shared prompts, compressed context, multiple context modes, etc.";

                notif = this.settingsLanguage == 'cn' ? notifcn : notifen;
                this.showNotification(notif, 'center', title);
            },
            //添加记忆分割线 Add <hr> for a new round of context.
            add_hr_newRoundContext(isUndo) {
                let lastInfoElem;
                let infoElems; // class = "aiinfo" or "userinfo"

                //界面上至少存在3条记录（即欢迎语 + 一次问答），才可能添加分割线
                if (this.msgList.length > 2) {
                    //判断最后一条记录属于哪边
                    if (this.msgList[this.msgList.length - 1]["my"]) {
                        infoElems = document.querySelectorAll('.userinfo'); // 获取class=userinfo
                    } else {
                        infoElems = document.querySelectorAll('.aiinfo'); // 获取class=aiinfo
                    }

                    if (isUndo) {
                        //当前操作是：撤销 Undo
                        // 只有单轮记忆为0 ，同时不存在发送失败记录时，才能添加横线<hr>, 否则不添加
                        if (this.msgContentForMsgList_SingleRound.length > 1) { //记忆不为0
                            return;
                        }
                    } else {
                        //当前操作是：清空记忆 清空上下文 Clear context
                        // Do nothing.
                    }

                    // 获取最后一个info。  ps：如果是撤销，class一定是aiinfo。如果是清空记忆，class有两种可能。
                    lastInfoElem = infoElems[infoElems.length - 1];

                    // 如果当前不存在有<hr>分割线，才允许添加，避免重复添加
                    // aiinfo/userinfo是子元素，需要操作的是其父元素<div>。 <div>与<hr>属于同一层级
                    if (lastInfoElem.parentElement.nextElementSibling.tagName != 'HR') {
                        //在this.msgList中记录分割线，恢复数据时需要用
                        this.msgList[this.msgList.length - 1]["hr"] = true;
                        //开始添加分割线
                        let newHr = document.createElement('hr');
                        newHr.classList.add('newcontext-hr');
                        newHr.style.marginBottom = this.settingsTime_WechatStyle ? '20px' : '33px'; //当前是否存在微信样式时间
                        newHr.style.display = this.settingsDividerLine ? 'block' : 'none'; //是否显示分割线
                        lastInfoElem.parentElement.insertAdjacentElement('afterend', newHr);
                        this.scrollToBottomView(); //添加后，滚动到最下方
                    }

                    let txt_lang1 = this.settingsLanguage == 'cn' ? '#### #无记忆状态，新对话开始\r\n***\r\n\r\n' : '#### #The context was emptied. New Chat begins.\r\n***\r\n\r\n';  //changeSelectedChatListMoreOptions(chatdata)中有用到，修改需要同步
                    this.chathistory += (txt_lang1);

                    //隐形提示词 如果在代码中写了gptSystemPrompt_hidden隐藏提示词，则不写入提示词。
                    if (this.gptSystemPrompt_hidden.trim() != '') {
                        // this.chathistory  +=
                    } else {
                        this.addSystemPromptToChatHistory('#### #当前系统提示词 system prompt ', '#### #Current System Prompt '); //this.chathistory
                    }

                }
            },
            //恢复对话数据时，添加所有的记忆分割线 When restoring Chat Data, add all <hr>
            add_hr_For_ChatDataRestored() {
                // 获取所有class等于msgdatetimediv的<div>元素
                const datetimeDIV = document.querySelectorAll('div.msgdatetimediv');

                // 遍历<div>元素
                datetimeDIV.forEach(timediv => {
                    // 获取data-hr属性的值
                    const hrValue = timediv.getAttribute('data-hr');
                    // 将字符串类型的布尔值转换成真正的布尔值
                    if (hrValue === 'true') {
                        let newHr = document.createElement('hr');
                        newHr.classList.add('newcontext-hr');
                        newHr.style.marginBottom = this.settingsTime_WechatStyle ? '20px' : '33px'; //当前是否存在微信样式时间
                        newHr.style.display = this.settingsDividerLine ? 'block' : 'none'; //是否显示分割线
                        timediv.parentElement.insertAdjacentElement('afterend', newHr);
                    }
                });

            },

            //按钮：撤销 Undo
            undo() {

                this.scrollToBottomView();
                //v5,11 修复逻辑错误、bug。全新逻辑，涉及：撤销+重问+发送失败+Tokens等。  v5,11 Fix bugs. Fix logic errors. Brand new logic. Regarding: undo, retry, send failed, tokens...
                if (this.msgContentForMsgList_SingleRound.length > 1) {
                    const txt_lang1 = this.settingsLanguage == 'cn' ? '### #撤销\r\n\r\n' : '### #Undo\r\n\r\n';
                    this.chathistory += (txt_lang1);
                    this.userMsgTokensForRetry = 0; //清零

                    let templastmsgHtml = this.msgList.pop(); //HTML
                    this.msgContentForMsgList_SingleRound.pop();

                    // 'my'：最后一条为user，一定是发送失败的msg。msgContent和msgTokens不会包含失败的msg，无需处理。msgList和msgContentForMsgList_SingleRound 刚刚pop()过，同样无需处理。
                    //If the last msg is 'my', it must be a failed message. msgContent and msgTokens do not contain failed msg, so there is no need to handle them.  msgList and msgContentForMsgList_SingleRound have already been deleted and do not need to be processed again.

                    // GPT / AI
                    if (!templastmsgHtml['my']) {
                        //处理msgContent和msgTokens中的AI msg
                        this.msgContent.pop();
                        this.msgTokens.pop();
                        //处理user msg
                        this.msgList.pop();
                        this.msgContentForMsgList_SingleRound.pop();
                        this.msgContent.pop();
                        this.msgTokens.pop();

                        this.succQA_Count = this.msgContent.length - 1; //int. 更新
                    }

                    if (this.msgContentForMsgList_SingleRound.length < 2) {
                        //msgContentForMsgList_SingleRound.length == 1 ( msgList.length== 1)时，撤销&重问按钮-不可用，清空按钮-不可用
                        this.btnDisabledState_Undo = true;
                        this.btnDisabledState_Clear = true;
                        if (this.sentext == '请重发' || this.sentext == 'Please Retry') {
                            this.sentext = this.settingsLanguage == 'cn' ? '发送' : 'Send';
                        }
                    } else if (this.msgContent.length < 2) {
                        //只要本轮记忆界面上还存在有msg(包含失败的msg)，撤销&重问 就保持可用状态
                        //但记忆可能提前为空 msgContent.length == 1，此时清空不可用。即，只剩有失败的msg时，清空不可用。
                        this.btnDisabledState_Clear = true;
                        this.btnDisabledState_Undo = false; //保持可用
                    }

                    // length-1 是最后一条AI回复的数据(无论隔着多少条发送失败的记录)，或是msgTokens[0]的数据(无记忆)
                    this.totaltokens = this.msgTokens[this.msgTokens.length - 1]["total_tokens"]; // int

                    this.$nextTick(() => {
                        this.add_hr_newRoundContext(true); //添加记忆分割线
                    });

                    //更新 保存 聊天数据 chatData chatDataList
                    //更新最后活动时间
                    this.updateChatLastTime(this.formatDateYYYYMMDDHHmmss(new Date(), ':'));//最后活动时间

                    if (this.msgContent.length == 0 || this.msgContentForMsgList_SingleRound.length == 0) {
                        console.error("undo(). length == 0. [Undo] 意外错误 Error bug");
                    }
                } else {
                    //err  If the code has reached here, it means there is an error.
                    console.error("undo(). length <= 1 && Undo is available. [Retry] 意外错误 Error bug");
                    this.btnDisabledState_Undo = true;
                    this.btnDisabledState_Clear = true;
                }
            },
            //按钮：重问 Retry *Undo and resend
            retry() {
                if (this.isTotaltokens_Exceeds_Maxtokens()) {
                    return;//当前totaltokens 已经超过模型的最大max_tokens数, 弹窗警告 + 返回
                }

                this.isRetry_RetryMessage = ''; //先重置

                //v5,11 修复逻辑错误、bug。全新逻辑，涉及：撤销+重问+发送失败+Tokens等。  v5,11 Fix bugs. Fix logic errors. Brand new logic. Regarding: undo, retry, send failed, tokens...
                if (this.msgContentForMsgList_SingleRound.length > 1) {
                    const txt_lang1 = this.settingsLanguage == 'cn' ? '### #重问\r\n\r\n' : '### #Retry\r\n\r\n';
                    this.chathistory += (txt_lang1);
                    let templastmsgHtml = this.msgList.pop(); //HTML. last msg - AI or ME/my/user
                    let templastmsgContent = this.msgContentForMsgList_SingleRound.pop();
                    let totaltokens_temp = 0;

                    if (!templastmsgHtml['my']) {
                        //templastmsgHtml is AI, then...
                        this.msgContent.pop(); //Context. del last msg - AI
                        this.msgTokens.pop(); //msgTokens. del last msg - AI
                        this.msgList.pop(); //HTML. del ME
                        this.msgContentForMsgList_SingleRound.pop(); //msgContentForMsgList_SingleRound
                        templastmsgContent = this.msgContent.pop(); //Context. del last msg - Me
                        this.succQA_Count = this.msgContent.length - 1; //int. 更新

                        //user. 在msgTokens.pop()前，先获取user的token总数。此时totaltokens一定不等于0
                        //已获得AI回复过的重问，tokens总数包含提问的内容（如果指令没有改动的话是准确的，指令改动过就不准）
                        totaltokens_temp = this.msgTokens[this.msgTokens.length - 1]["total_tokens"];  // int
                        this.userMsgTokensForRetry = totaltokens_temp; //临时记录tokens。本次重问失败后，再次重问时会用到
                        this.msgTokens.pop(); //msgTokens. del last msg - Me
                        if (this.msgContent.length == 0 || this.msgTokens.length == 0) {
                            alert(" 找到一个bug\r\nA new bug has appeared.\r\n\r\n已回复后的重问，在两次pop()后 this.msgContent.length 和 this.msgTokens.length 都不该为0 ，此时应该至少有1条数据，([0] = System Prompt 系统指令)。 \r\n\r\nthis.msgContent.length: " + this.msgContent.length + "\r\n\r\nthis.msgTokens.length: " + this.msgTokens.length);
                        }

                    } else {
                        //templastmsgHtml is Me, it must be a failed message. 最后一条为发送失败的msg

                        // length-1 是最后一条AI回复的数据(无论隔着多少条发送失败的记录)，或是msgTokens[0]的数据(无记忆)
                        totaltokens_temp = this.msgTokens[this.msgTokens.length - 1]["total_tokens"];  // int
                        if (this.userMsgTokensForRetry != 0) {
                            // != 0： 说明是曾经AI回复过，且已经重试过，但重试失败了。（重问成功，userMsgTokensForRetry会被清零）
                            // != 0： AI replied before and has already been retried, but the retry failed. (If the retry succeeds, userMsgTokensForRetry will be reset to zero.)
                            totaltokens_temp = this.userMsgTokensForRetry;
                        }
                    }

                    this.totaltokens = totaltokens_temp;


                    if (this.msgContent.length == 0 || this.msgContentForMsgList_SingleRound.length == 0) {
                        console.error("retry(). length == 0. [Retry] 意外错误 Error bug");
                    }

                    //'templastmsgContent' Last sent question. (Not processed by marked.js + highlight.js.)
                    this.isRetry_RetryMessage = templastmsgContent['content']; //新的方法(new version)，作用：[重问]会替换和清空聊天输入框，新版可避免这种潜在的丢失数据的风险。

                    //必须用$nextTick(()。 Tips：清空记忆后，会产生分割线，此时问题发出后，中间会显示时间，此时点击重问，居中的WeChatStyleDatetime不会随着pop()消失。加上$nextTick后，等待pop()执行/修改DOM/渲染...彻底完成后，居中的时间会被顺利删除，此时再重问。
                    this.$nextTick(() => {
                        this.sendMsg();
                    });
                } else {
                    //err  If the code has reached here, it means there is an error.
                    console.error("retry(). length <= 1 && Retry is available. [Retry] 意外错误 Error bug");
                    this.btnDisabledState_Undo = true;
                    this.btnDisabledState_Clear = true;
                }
            },
            //按钮：点击验证API Key  Check API Key
            checkAPIbtn() {

                console.log('suss');
                debugger;
                let notif = '';
                this.api = this.api.trim();

                if (this.api == "") {
                    this.sentext = this.settingsLanguage == 'cn' ? '先验证API' : 'Check API First';
                    notif = this.settingsLanguage == 'cn' ? '请填入 API Key' : 'Please enter the API Key';
                    this.showNotification(notif, 'bottom');
                    this.restoreApiKeyInput(true, true);//询问是否恢复key
                    return;
                }

                //是否正在发送
                if (this.isSendingNow) {
                    return;
                }

                //保存 获取最新API Key数据 快速验证
                this.saveApikeyData();
                const isExist = this.apiCheckedData.some(item => item.apikey.trim() == this.api);
                if (isExist) {//如果已经存在
                    this.inputapichange(true);
                    return;
                }

                const currentTime = new Date().getTime(); // 获取当前时间
                if (currentTime - this.lastCheckApiTime < 4000) {
                    // 与上一次点击时间比较，小于4秒则返回
                    notif = this.settingsLanguage == 'cn' ? '请勿频繁发送请求，需间隔4秒' : 'Requests too fast, please wait 4 seconds between each one.';
                    this.showNotification(notif, 'bottom', '', 2600);
                    return;
                }
                // 4秒内只能提交一次验证，防止过快重复点击
                this.lastCheckApiTime = currentTime; // 更新上一次点击时间

                this.isCheckingApiKeyNow = true; //正在验证中，之后不能再有"return"代码
                this.btnDisabledState_ApiURL = true;
                this.btnDisabledState_Language = true;
                this.btnDisabledState_ContextualMode = true;

                this.beginLoadingAnimation();
                document.querySelector('.inputapikey').readOnly = true; //验证中，apikey禁止改动
                notif = this.settingsLanguage == 'cn' ? '验证中...' : 'Checking...';
                this.lastAPIKeyInputText = this.api;//更新最后一次输入框中的key，无论是否成功
                //测试 console.log('验证 - 当前api ',this.lastAPIKeyInputText);

                //为false，表示新窗口从未写入过指令，提醒一次。
                if (!this.hasSystemPromptBeenSaved) {
                    if (this.gptSystemPrompt.trim() != "" && this.gptSystemPromptReadOnly.trim() == "") {
                        //当gptSystemPrompt(编辑框)有内容 + gptSystemPromptReadOnly为空（当前系统提示词为空），提醒用户有未保存的提示词。
                        //隐形提示词 仅当gptSystemPrompt_hidden隐藏提示词不存在时
                        if (this.gptSystemPrompt_hidden.trim() == '') {
                            notif += this.settingsLanguage == 'cn' ? '    提示: [系统提示词]需要保存后才会生效' : '    Tip: [System Prompts] will only take effect after being saved.';
                        }
                        this.hasSystemPromptBeenSaved = true;
                    } else {
                        this.hasSystemPromptBeenSaved = true;
                    }
                }

                //弹出通知
                this.showNotification(notif, 'bottom', '', 60000);

                //用随机字符串验证api，考虑: 使用人数多时，重复发单一内容给OpanAI服务器，会受惩罚
                let arrayStr = ["hi~", "Hey ChatGPT", "hi", "hello", "Hi, how are you?", "What's up?", "Hello there!", "How are you", "Hey!", "Sup?", "Yo!", "Howdy!", "Hey?", "Yo~", "Hiya", "Hello GPT, what's up?", "Hey GPT"];
                let randomString = 'hi';
                if (arrayStr.length > 0) {
                    randomString = arrayStr[Math.floor(Math.random() * arrayStr.length)];
                }
                if (typeof randomString !== 'string') {
                    randomString = 'hi';
                }

                // 设置axios的超时时间，默认是0（永不超时，实际上60秒就会报超时）。验证API时，请求的问题很简单，超时时间可以短点，设为35秒。
                // To set a timeout for axios, the default is 0 . For simple request issues when checking the API, the timeout can be set to 35 seconds or less.
                // *Default is 0 (Never timeout,In actuality, it will report a timeout error in around 60 seconds.)
                axios.defaults.timeout = 35000; //35000毫秒=35秒。  35000ms = 35s

                // Check API Key, not chat
                //下面这行是验证API Key时的参数，不是聊天的参数，一般不需要改动
                axios.post(this.apiURL, {
                    messages: [{"role": "user", "content": randomString}], max_tokens: 30, model: this.apiGPTModel
                }, {
                    headers: {'content-type': 'application/json', 'Authorization': 'Bearer ' + this.api}
                }).then(res => {
                    //console.log('suss',res);
                    this.isCheckingApiKeyNow = false; //更新验证的状态
                    this.endLoadingAnimation();
                    this.lastAPIKeyInputText_Available = this.api;//更新最后一次输入框中的key，成功的
                    document.querySelector('.inputapikey').readOnly = false; //验证结束，apikey恢复可改


                    this.changeButtonDisabledState(false);//改变按钮状态

                    this.btnDisabledState_ApiURL = false;
                    this.btnDisabledState_Language = false;
                    this.btnDisabledState_ContextualMode = false;

                    notif = this.settingsLanguage == 'cn' ? '验证成功，API Key 可用' : 'Successful, API Key is valid.';
                    this.showNotification(notif, 'bottom', '', 1800);


                    //更新this.apiCheckedData  记录已验证的API Key信息
                    this.updateApiCheckedData(this.api, currentTime, 'success');


                    //以上：每次验证成功/失败都会改变的状态
                    //以下：首次验证成功后就不再改变的状态，相当于初始化

                    // none

                }).catch(error => {
                    console.log('error', error);
                    this.isCheckingApiKeyNow = false; //更新验证的状态
                    this.endLoadingAnimation();
                    document.querySelector('.inputapikey').readOnly = false; //验证结束，apikey恢复可改

                    //API Key可重复验证，虽限制4秒才能发送一次请求，但仍然会出现这种情况：服务器慢，同时存在多次请求，当1个请求通过验证后，第2次或之后的可能因为“短时间请求过快”而未通过，造成多个控件又变成不可用的状态。
                    //通过以下逻辑可以解决这个问题：当报错时，代码执行到此处，this.btnDisabledState_Sending==false  或 this.btnDisabledState_CheckAPI==true 都可以说明刚通过了验证
                    if (!this.btnDisabledState_Sending && this.btnDisabledState_CheckAPI) {
                        return;//已存在验证通过的Key，忽略报错
                    }

                    //以上是必须执行的

                    //以下

                    this.changeButtonDisabledState(true);//改变按钮状态

                    this.btnDisabledState_ApiURL = false;
                    this.btnDisabledState_Language = false;
                    this.btnDisabledState_ContextualMode = false;

                    if (error.message && error.message.includes('Type error')) {
                        //密钥含非法符号、全角符号等 illegal symbols
                        this.sentext = this.settingsLanguage == 'cn' ? 'API Key错误' : 'API Key Error';
                    } else if (error.message && error.message.includes('401')) {
                        // 401-1 & 401-2 密钥无效 API Key Error
                        this.sentext = this.settingsLanguage == 'cn' ? 'API Key错误' : 'API Key Error';
                    } else {
                        // 其他情况 other
                        this.sentext = this.settingsLanguage == 'cn' ? '先验证API' : 'Check API First';
                    }

                    //错误代码 弹窗
                    this.catchApiErrorCode(error, currentTime);

                })
            },
            //验证&发送的错误代码 错误弹窗 Check Send Err
            catchApiErrorCode(error, requesttime) {
                //末尾有代码this.isSendingNow_ForSmartMaxTokens = false; 如果代码中有添加return，那么return前，需要加上这句更新发送状态的代码。*主要用于区别“发送”和“验证”，验证不需要SmartMaxTokens

                const current_apidata = this.apiCheckedData.find(data => data.apikey === this.api);//当前apidata
                //current_apidata == null说明是“点击[验证]按钮验证的新key”，不是“导入或批量添加的新Key”。 区别：导入或批量添加的Key已经被添加到this.apiCheckedData，虽然未验证过。
                //更新时间updatetime == 0，说明是未检测的新Key。 *另一个属性level不能用于判断新Key，level==null 不能代表是未检测过的(使用过程中，某些情况下level会被改为null)，只有 updatetime 可以判断。
                const isKeyNew_From_Import_or_BulkAdd = (current_apidata != null && current_apidata.updatetime === 0) ? true : false;

                let delaytime = 450; //450ms ，450毫秒
                let notif_cn, notif_en, title; //中，英，标题
                let responseObject; //responseText
                let responseMessage = ''; //OpenAI Error Message
                let responseType = ''; //OpenAI Error Type
                const timeoutRegExp = /timeout of \d+ms exceeded/; // “timeout of 30000ms exceeded” , “timeout of 60000ms exceeded”

                // OpenAI官网关于错误代码的解释： OpenAI - API Error Codes Explained:
                // https://help.openai.com/en/collections/3808446-api-error-codes-explained

                if (error.request && error.request.responseText) {
                    responseObject = JSON.parse(error.request.responseText); // 将 JSON 字符串解析为 JavaScript 对象
                    responseMessage = responseObject.error.message ? responseObject.error.message : ''; // 读取 message 属性的值
                    responseType = responseObject.error.type ? responseObject.error.type : ''; // 读取 type 属性的值
                }

                let apidata_errorState = '';//用于更新this.apiCheckedData
                let autoRetry_errortype = '';//用于自动重问/自动重试
                let autoRetry_bottomnotifCN = '';//用于自动重问/自动重试 简短通知
                let autoRetry_bottomnotifEN = '';//用于自动重问/自动重试 简短通知

                const model_data = this.gptModelList.find(model => model.modelname === this.apiGPTModel);

                //判断错误类型
                if (error.message && error.message.includes('Type error')) {
                    //密钥含非法符号、全角符号等 illegal symbols
                    notif_cn = 'API Key 含有非法符号。OpenAI的所有密钥均以 sk- 开头，不含有汉字、全角符号或其他非法符号。建议复制粘贴 API Key，避免意外输入全角符号或其他非法符号。';
                    notif_en = 'All the OpenAI keys begin with sk- as the first three characters, and do not contain any full-width symbols, or other illegal symbols.';
                    title = this.settingsLanguage == 'cn' ? '错误 - API Key 格式非法' : 'Error - Invalid API Key Format';
                    apidata_errorState = '';//用于更新this.apiCheckedData
                } else if (error.message && (error.message == 'timeout exceeded' || timeoutRegExp.test(error.message))) {
                    // 网络超时 Timeout exceeded
                    // if判断需要先于"OpenAI错误代码"，因为超时时间中可能会包含有400、500等数字。
                    // The if statement needs to come before "OpenAI's error code" because the error codes for timeout may include numbers such as 400 or 500.
                    notif_cn = '连接超时，请先检查您的网络状态。如果网络正常，需要确认您的IP所在地（国家或地区）是否属于OpenAI的服务范围内。如果您在国内使用OpenAI官方接口，必须使用"魔法"才能建立连接。如果您使用第三方接口，需要明确该接口是否支持免"魔法"，以及该接口当前是否为可用状态（第三方稳定性不如官方）。\r\n\r\nOpenAI支持的国家或地区:(需魔法) https://platform.openai.com/docs/supported-countries \r\n什么是"魔法"？一种可以让您的手机或电脑出趟国的技术，优质的魔法会让OpenAI认为您的设备是真的在国外。';
                    notif_en = "Connection timed out. Please check your network and confirm whether your IP location is within OpenAI's service scope to decide whether V P N is required. If using third-party interfaces, ensure their validity. \r\n\r\nSupported countries and territories:  https://platform.openai.com/docs/supported-countries ";
                    if (timeoutRegExp.test(error.message)) {
                        const timeRegExp = /timeout of (\d+)ms exceeded/;
                        const matchResult = error.message.match(timeRegExp);
                        if (matchResult) {
                            const timeoutSec = matchResult[1] / 1000;
                            notif_cn += "\r\n当前代码中设置的超时时间为：" + timeoutSec + " 秒";
                            notif_en += "\r\nThe timeout set in the code is: " + timeoutSec + " seconds";
                        }
                    }
                    title = this.settingsLanguage == 'cn' ? '错误 - 网络连接超时' : 'Error - Timeout exceeded';
                    apidata_errorState = '';//用于更新this.apiCheckedData
                    autoRetry_errortype = 'timeout exceeded';
                    autoRetry_bottomnotifCN = '连接超时，自动重发中...';
                    autoRetry_bottomnotifEN = 'Connection timed out, automatically retrying...';
                } else if (error.message && error.message == 'Network Error') {
                    // 网络故障 Network Error *第三方接口每分钟超3次也会导致这个错误(6月第三方接口好像有升级过，规则变了)
                    notif_cn = '网络错误，可能只是暂时的，建议先重试一次。\r\n网络错误存在多种可能，请自行排查：\r\n  • 无法与接口网址（API url）建立连接，该服务器不存在或遇故障当前无法访问；\r\n  • 本机当前无网络，网络已断开或网络不稳定，请检查网络状态后重试；\r\n  • IP限制，该IP或其所在地被限制访问。可尝试使用"魔法"，如果正在使用，请检查"魔法"是否不稳定或已经断开；\r\n  • 若使用第三方接口，发送请求太快可能也会出现网络错误的代码（Network Error），请尝试减缓发送频率；\r\n  • 其他原因，请以实际情况为准。';
                    notif_en = 'Network error. The error may be temporary, so it is recommended to try again first. \r\nPossible reasons:\r\n  • Unable to connect to the API Endpoint (API url). The server does not exist or is not accessible due to a malfunction.\r\n  • Your device is currently not connected to the network. The network may be disconnected or unstable. Please check the network status and try again.\r\n  • Due to limitations on this IP or its location, access may be restricted. You could try using a V P N. If you are already using a V P N, please check if it is unstable or has been disconnected.\r\n  • When using third-party interfaces, sending requests too quickly may also result in "Network Error". Please avoid sending requests too fast.\r\n  • For any other reasons, please refer to the actual situation.';
                    title = this.settingsLanguage == 'cn' ? '错误 - 网络故障' : 'Error - Network error';
                    apidata_errorState = '';//用于更新this.apiCheckedData
                    autoRetry_errortype = 'Network Error';
                    autoRetry_bottomnotifCN = '网络错误，自动重发中...';
                    autoRetry_bottomnotifEN = 'Network error, automatically retrying...';
                } else if (error.message && error.message.includes('401')) {
                    // OpenAI 401-1 & 401-2 密钥无效 API Key Error
                    notif_cn = 'API Key 无效。OpenAI的所有密钥均以 sk- 开头，建议复制粘贴 API Key，避免输错。如果仍然出错，说明是无效密钥，或密钥已被停用、删除，建议登录OpenAI官网查看 API Key 是否出现变动。';
                    notif_en = 'API Key is invalid. All the OpenAI keys begin with sk- as the first three characters. It is recommended to copy and paste the API Key to avoid input errors. If the error persists, it indicates an invalid API key or that the API key has been deactivated or deleted, please log in to OpenAI to check the API Key.';
                    title = this.settingsLanguage == 'cn' ? '错误 - 401 无效的 API Key' : 'Error - 401 API Key is invalid';
                    apidata_errorState = '401';//用于更新this.apiCheckedData *如果是发送状态下的401，标记key为无效(level=8)

                    //如果是发送Send，而非验证Check。建议删除无效的Key
                    if (this.isSendingNow_ForSmartMaxTokens === true) {
                        notif_cn += '\r\n\r\n  • 如果您在 OpenAI 后台确认此 API Key 已失效或已被删除，请考虑在 LinGPT 中删除此  API Key，因为它无法恢复。'
                        notif_en += '\r\n\r\n  • If you have confirmed in the OpenAI backend that this API Key is invalid or has been deleted, please consider removing this API Key from LinGPT as it cannot be recovered.'
                    }

                    if (isKeyNew_From_Import_or_BulkAdd) { //首次检测，允许自动重问。*非首次，是不允许重问的，出错说明key出问题，需要告知用户key状态出现变动
                        autoRetry_errortype = 'FirstCheckedKey_401';
                        autoRetry_bottomnotifCN = '全新Key - 密钥无效，自动重发中...';
                        autoRetry_bottomnotifEN = 'New Key - API Key error, automatically retrying...';
                    }
                } else if (error.message && error.message.includes('429')) {
                    // OpenAI 429 x 5   *v7.05 新增“429-5”
                    if (responseMessage.includes('Rate limit reached for') && responseType == 'requests') {
                        // 429-1 请求过快(频率过快) Rate limit (requests)  *免费:3次/分钟 200次/天   付费开通48小时内:60次/分钟 2000次/天  付费开通48小时后:3500次/分钟
                        notif_cn = '请求过于频繁，API速率限制已达上限，短时间内提交了过多的请求，超出允许的请求数量。';
                        notif_en = 'The API rate limit has been reached, possibly due to submitting too many requests in a short period of time, exceeding the allowed number of requests.';
                        // https://platform.openai.com/docs/guides/rate-limits/overview
                        if (responseMessage.includes('Limit: 3 / min')) { //免费每分钟3次
                            notif_cn = notif_cn + "\r\n\r\n  • 目前，OpenAI 免费账号的 API Key 限制每分钟最多 3 次请求。实际使用时，API Key 超过 1 分钟没有发起任何请求，那么下次使用时，连发 3 次请求将不受限制，3 次后连续的快速的请求会有 20 秒的限制。";
                            notif_cn = notif_cn + "\r\n  • 同一账号下的多个 API Key 会共享限制。同理，“超过 1 分钟没有发起任何请求”这个条件也是指同一账号的所有 API Key 均未发过请求。";
                            notif_en = notif_en + "\r\n\r\n  • For free accounts, OpenAI allows a maximum of 3 API calls per minute (this limit is shared among multiple API keys under the same account). If an API key under the same account remains inactive for more than 1 minute, the next 3 consecutive requests will be unrestricted. However, after the 3rd request, there will be a 20-second limit on consecutive rapid requests.";
                        } else if (responseMessage.includes('Limit: 60 / min')) { //付费账号(48小时内)每分钟60次
                            notif_cn = notif_cn + "\r\n\r\n  • 目前，OpenAI 付费账号 (开通48小时内) 的 API Key 限制每分钟最多 60 次请求（GPT3.5模型）。";
                            notif_cn = notif_cn + "\r\n  • 同一账号下的多个 API Key 会共享限制。";
                        } else if (responseMessage.includes('Limit: 3500 / min')) { //付费账号(48小时之后)每分钟60次
                            notif_cn = notif_cn + "\r\n\r\n  • 目前，OpenAI 付费账号 (开通48小时后) 的 API Key 限制每分钟最多 3500 次请求（GPT3.5模型）。";
                            notif_cn = notif_cn + "\r\n  • 同一账号下的多个 API Key 会共享限制。";
                        } else if (responseMessage.includes('Limit: 200 / day')) { //免费账号 每天200次上限
                            notif_cn = notif_cn + "\r\n\r\n  • 目前，OpenAI 的免费账号限制 API 每天最多 200 次请求（GPT3.5模型）,之后每隔 7分12秒 恢复 1 次请求额度。";
                            notif_cn = notif_cn + "\r\n  • 官方算法：24小时 ÷ 200次 = 7分12秒/次";
                            notif_cn = notif_cn + "\r\n  • 同一账号下的多个 API Key 会共享限制。";
                        } else if (responseMessage.includes('Limit: 2000 / day')) { //付费账号(开通48小时内) 每天2000次上限。48小时后，只有每分钟的上限限制，没有每天的上限限制。
                            notif_cn = notif_cn + "\r\n\r\n  • 目前，OpenAI 的付费账号 (开通48小时内) 限制 API 每天最多 2000 次请求（GPT3.5模型）,之后应该是每隔约 43 秒恢复 1 次请求额度（具体情况请查看 [OpenAI - API错误信息]）。";
                            notif_cn = notif_cn + "\r\n  • 同一账号下的多个 API Key 会共享限制。";
                        }
                        title = this.settingsLanguage == 'cn' ? '错误 - 429 请求速度过快' : 'Error - 429 Rate limit reached for requests';
                        if (responseMessage.includes('Limit: 200 / day')) { //免费账号达到每天的上限后，nexttime需要加7分12秒 （不考虑刚开通48小时的付费账号）
                            apidata_errorState = '429-1-day';//用于更新this.apiCheckedData
                        } else {
                            apidata_errorState = '429-1';//用于更新this.apiCheckedData
                        }
                        autoRetry_errortype = '429-1'; //所有情况，均允许自动轮询重问
                        autoRetry_bottomnotifCN = '请求过于频繁，API速率限制已达上限，自动重发中...';
                        autoRetry_bottomnotifEN = 'The API rate limit has been reached for requests, automatically retrying...';

                        //Smart max_tokens/智能max_tokens导致错误时，自动修复 并自动重问，此时连续两次请求（出错+立马重问）很容易429 请求过快 Rate limit。所以，这种情况下，有两个弹窗，429做为第二个弹窗，需要延迟弹出。
                        //1 判断依据：this.isFixingMaxTokens_SmartMaxTokens   2 只延迟一次，之后手动点击重问，再次429报错时不延迟： this.isFirstRetry_SmartMaxTokens
                        delaytime = this.isFixingMaxTokens_SmartMaxTokens && this.isFirstRetry_SmartMaxTokens ? 5000 : delaytime;
                        this.isFirstRetry_SmartMaxTokens = false;
                    } else if (responseMessage.includes('You exceeded your current quota') || responseType == 'insufficient_quota') {
                        // 429-2 超出配额 Insufficient quota
                        notif_cn = '超出配额，账号已无额度。例如：免费账号超额或到期、账号达到月度最大支出限制、账号达到设定的周期限制...';
                        notif_en = 'Exceeding current quota, such as reaching maximum monthly spend, exceeding free plan limits, or reaching expiration.';
                        //如果是验证API key
                        if (this.isSendingNow_ForSmartMaxTokens === false) {
                            //借用isSendingNow_ForSmartMaxTokens，如果为false，表示当前是[验证API Key]时出错,而不是[发送]时出错。 [Check API Key]出错时需要提醒"过期key会被保存，及删除过期key的方式"。
                            notif_cn = notif_cn + "\r\n\r\n  • 此 API Key 已保存，但由于超出配额，目前无法使用。在 OpenAI 官网为其续费或开通付费后，请在 LinGPT 中手动更新其状态以恢复可用。如果不再使用该 API Key，也可以手动删除（不删除也不会有影响）。";
                            notif_en = notif_en + "\r\n\r\n  • The API Key has been saved, but it cannot be used due to exceeding the current quota. If you have changed the plan or renewed it in OpenAI, please manually mark it as available in the API key management feature of LinGPT. If you no longer use this API Key, you can also manually delete it (not deleting it will not have any impact).";

                            //以下参考“验证成功”的代码，手动调成“验证成功”的状态
                            //7.16 去除“验证成功”和“验证失败”中的重复代码，只需要两行代码即可
                            if (this.lastAPIKeyInputText_Available === '') { //加个判断，如果存在，保持不变。
                                this.lastAPIKeyInputText_Available = this.api;//更新最后一次输入框中的key，成功的
                            }
                            this.changeButtonDisabledState(false);//改变按钮状态

                        }
                        title = this.settingsLanguage == 'cn' ? '错误 - 429 配额不足' : 'Error - 429 Insufficient quota';
                        apidata_errorState = '429-2';//用于更新this.apiCheckedData

                        //当前是新Key，遇到错误允许自动重问，自动验证下一个新的key(如果存在)
                        if (isKeyNew_From_Import_or_BulkAdd) { //首次检测，允许自动重问。*非首次，是不允许重问的，出错说明key出问题，需要告知用户key状态出现变动
                            autoRetry_errortype = 'FirstCheckedKey_429-2';
                            autoRetry_bottomnotifCN = '全新Key - 配额不足，自动重发中...';
                            autoRetry_bottomnotifEN = 'New Key - Insufficient quota, automatically retrying...';
                        }
                    } else if (responseMessage.includes('The engine is currently overloaded')) {
                        // 429-3 服务器过载 Servers overloaded
                        notif_cn = '服务器正在面临着高流量访问，或正在维护更新，当前繁忙，无法处理请求。请稍等片刻后重试。';
                        notif_en = 'Servers are experiencing high traffic and are unable to process your request at the moment. Please retry your requests after a brief wait.';
                        title = this.settingsLanguage == 'cn' ? '错误 - 429 服务器过载' : 'Error - 429 Servers overloaded';
                        apidata_errorState = '429-3';//用于更新this.apiCheckedData
                        autoRetry_errortype = '429-3';
                        autoRetry_bottomnotifCN = '服务器过载，自动重发中...';
                        autoRetry_bottomnotifEN = 'Servers overloaded，automatically retrying...';
                    } else if (responseMessage.includes('That model is currently overloaded with other requests')) {
                        // 429-4 模型超载  Model overloaded  tips: responseType = server_error
                        notif_cn = '该模型当前已经超载，请稍等片刻后重试。';
                        notif_en = 'That model is currently overloaded with other requests. Please retry your requests after a brief wait.';
                        title = this.settingsLanguage == 'cn' ? '错误 - 429 模型超载' : 'Error - 429 Model overloaded';
                        apidata_errorState = '429-4';//用于更新this.apiCheckedData
                        autoRetry_errortype = '429-4';
                        autoRetry_bottomnotifCN = '模型超载，自动重发中...';
                        autoRetry_bottomnotifEN = 'Model overloaded，automatically retrying...';
                    } else if (responseMessage.includes('Rate limit reached for') && responseType == 'tokens') {
                        // 429-5 请求过快(令牌tokens过快 每分钟) Rate limit (tokens)   *v7.05 add
                        notif_cn = '令牌（tokens）请求过于频繁，API速率限制已达上限，短时间内提交了过多的令牌（tokens），超出允许的请求数量。';
                        notif_en = 'The API rate limit has been reached, possibly due to submitting too many tokens in a short period of time, exceeding the allowed number of requests.';
                        // https://platform.openai.com/docs/guides/rate-limits/overview
                        if (responseMessage.includes('Limit: 40000 / min')) {
                            notif_cn = notif_cn + "\r\n\r\n  • OpenAI 的免费账号限制 API 每分钟最多 40000 令牌（GPT3.5模型），请稍后重试。";
                            notif_cn = notif_cn + "\r\n  • 同一账号下的多个 API Key 会共享限制。";
                        } else if (responseMessage.includes('Limit: 60000 / min')) {
                            notif_cn = notif_cn + "\r\n\r\n  • OpenAI 的付费账号 (开通48小时内) 限制 API 每分钟最多 60000 令牌（GPT3.5模型），请稍后重试。";
                            notif_cn = notif_cn + "\r\n  • 同一账号下的多个 API Key 会共享限制。";
                        } else if (responseMessage.includes('Limit: 90000 / min')) {
                            notif_cn = notif_cn + "\r\n\r\n  • OpenAI 的付费账号 (开通48小时后) 限制 API 每分钟最多 90000 令牌（GPT3.5模型），请稍后重试。";
                            notif_cn = notif_cn + "\r\n  • 同一账号下的多个 API Key 会共享限制。";
                        }
                        title = this.settingsLanguage == 'cn' ? '错误 - 429 令牌（tokens）的请求速度过快' : 'Error - 429 Rate limit reached for tokens';
                        apidata_errorState = '429-5';//用于更新this.apiCheckedData
                        autoRetry_errortype = '429-5';
                        autoRetry_bottomnotifCN = '请求过于频繁，令牌 (tokens) 的API速率限制已达上限，自动重发中...';
                        autoRetry_bottomnotifEN = 'The API rate limit has been reached for tokens, automatically retrying...';

                        // 429-1中智能max_tokens的代码 *不考虑，几率小
                    } else {
                        // 429-other 其他未知
                        notif_cn = 'HTTP请求 - 错误代码:  ' + error.code + '\r\nHTTP请求 - 错误信息:  ' + error.message;
                        notif_en = 'HTTP Error Code:  ' + error.code + '\r\nHTTP Error Message:  ' + error.message;
                        title = this.settingsLanguage == 'cn' ? '错误 - 429 其他' : 'Error - 429 Other';
                        apidata_errorState = '429-other';//用于更新this.apiCheckedData
                    }
                } else if (error.message && error.message.includes('404')) {
                    // OpenAI 404 非组织成员 Account is not part of an organization
                    notif_cn = '必须是组织的成员才能使用API，而当前账号不属于任何组织。OpenAI官方解释：https://help.openai.com/en/articles/6891827-error-code-404-you-must-be-a-member-of-an-organization-to-use-the-api ';
                    notif_en = 'To use the API, you must be a member of an organization, and your current account is not part of an organization. OpenAI API Error Codes Explained: https://help.openai.com/en/articles/6891827-error-code-404-you-must-be-a-member-of-an-organization-to-use-the-api ';
                    title = this.settingsLanguage == 'cn' ? '错误 - 404 账号不属于任何组织' : 'Error - 404 Account is not part of an organization';
                    apidata_errorState = '404';//用于更新this.apiCheckedData

                    if (isKeyNew_From_Import_or_BulkAdd) { //首次检测，允许自动重问。*非首次，是不允许重问的，出错说明key出问题，需要告知用户key状态出现变动
                        autoRetry_errortype = 'FirstCheckedKey_404';
                        autoRetry_bottomnotifCN = '全新Key - Key错误，非组织成员，自动重发中...';
                        autoRetry_bottomnotifEN = 'New Key - Error, Account not in organization, automatically retrying...';
                    }
                } else if (error.message && error.message.includes('500')) {
                    // OpenAI 500 服务器问题 Server error
                    notif_cn = '服务器在处理您的请求时出现错误，是服务器的问题，请稍等片刻后重试。';
                    notif_en = 'The server had an error while processing your request. Issue on OpenAI servers, please retry your requests after a brief wait.';
                    title = this.settingsLanguage == 'cn' ? '错误 - 500 服务器在处理请求时出错' : 'Error - 500 Server error while processing request';
                    apidata_errorState = '500';//用于更新this.apiCheckedData
                    autoRetry_errortype = '500';
                    autoRetry_bottomnotifCN = '服务器在处理请求时出错，自动重发中...';
                    autoRetry_bottomnotifEN = 'Server error while processing request，automatically retrying...';
                } else if (error.message && error.message.includes('503') && responseMessage.includes('That model is currently overloaded with other requests')) {
                    // OpenAI 503 - 1 服务器问题
                    notif_cn = '该模型当前已经超载，您可以重新尝试下请求。';
                    notif_en = 'That model is currently overloaded with other requests. You can retry your request.';
                    title = this.settingsLanguage == 'cn' ? '错误 - 503 模型超载' : 'Error - 503 Model overloaded';
                    apidata_errorState = '503';//用于更新this.apiCheckedData
                    autoRetry_errortype = '503';
                    autoRetry_bottomnotifCN = '模型超载，自动重发中...';
                    autoRetry_bottomnotifEN = 'Model overloaded，automatically retrying...';
                } else if (error.message && error.message.includes('503') && responseMessage.includes('The server had an error while processing your request')) {
                    // OpenAI 503 - 2 服务器问题  //type: server_error
                    notif_cn = '服务器在处理您的请求时发生了错误，您可以重新尝试下请求。';
                    notif_en = 'The server had an error while processing your request. You can retry your request.';
                    title = this.settingsLanguage == 'cn' ? '错误 - 503 服务器在处理请求时出错' : 'Error - 503 Server error while processing request';
                    apidata_errorState = '503';//用于更新this.apiCheckedData
                    autoRetry_errortype = '503';
                    autoRetry_bottomnotifCN = '服务器在处理请求时出错，自动重发中...';
                    autoRetry_bottomnotifEN = 'Server error while processing request，automatically retrying...';
                } else if (error.message && error.message.includes('400') && responseMessage.includes('Please reduce the length of the messages')) {
                    // OpenAI 400
                    //20230606 适配GPT3.5免费计划，其他类似账号的情况未知。 Adapted for the free plan account of GPT3.5, based on the GPT3.5 free plan.

                    let tips_cn = '';
                    let tips_en = '';

                    const maxLengthRegex = /maximum context length is (\d+) tokens/;
                    const maxLengthMatch = responseMessage.match(maxLengthRegex);
                    const maxLength = maxLengthMatch ? maxLengthMatch[1] : null;

                    const a_messagesRegex = /messages resulted in (\d+) tokens/;
                    const a_messagesMatch = responseMessage.match(a_messagesRegex);
                    const a_messagesLength = a_messagesMatch ? a_messagesMatch[1] : null;

                    const b_requestedRegex = /requested (\d+) tokens \(/;
                    const b_requestedMatch = responseMessage.match(b_requestedRegex);
                    const b_requestedLength = b_requestedMatch ? b_requestedMatch[1] : null;

                    const b_messagesRegex = /\((\d+) in the messages,/;
                    const b_messagesMatch = responseMessage.match(b_messagesRegex);
                    const b_messagesLength = b_messagesMatch ? b_messagesMatch[1] : null;

                    const b_completionRegex = /, (\d+) in the completion/;
                    const b_completionMatch = responseMessage.match(b_completionRegex);
                    const b_completionLength = b_completionMatch ? b_completionMatch[1] : null;

                    if (maxLength != null && a_messagesLength != null) {
                        //发送出的上下文超限制
                        tips_cn += '\r\n\r\n';
                        tips_cn += '#A = 当前模型支持的最大上下文长度：' + maxLength + ' tokens\r\n';
                        tips_cn += '#B = 您当前发送的上下文长度：' + a_messagesLength + ' tokens (GPT的记忆，包含本次发送内容)\r\n';
                        tips_cn += '#C = GPT尚未回复的单条内容长度：未知 \r\n';
                        tips_cn += '规则： B + C  < A ( ' + maxLength + ' )\r\n';
                        tips_cn += '★ 当前：仅 B 就已经超过 ' + maxLength + ' （超出了 ' + (a_messagesLength - maxLength) + ' ）';
                        if (!this.isSimpleMode) {
                            tips_cn += '\r\n\r\n  • token：单位，即"令牌"';
                            tips_cn += '\r\n  • 当前 GPT 模型 (' + model_data.modelname + ') 最高支持 ' + model_data.maxtokens + '/' + (model_data.maxtokens + 1) + ' tokens';
                            //completion_tokens(本次提问内容的token数)需要等下次对话结束才能计算得出（提问的token数会比实际的高，疑似官方会添加隐藏的提示词。意义大不，仅做参考）经过测试，1、当this.msgContent包含系统提示词这一项时（[0]，即使为空），首条会额外占用 13 token ； 2、之后每条发送的信息额外占用 10 token 。
                            tips_cn += '\r\n  • OpenAI官方行为：首条消息额外占用 13 个token，之后的每条消息都会额外加 10 个token (发送只有 1 个token的内容，实际上会占用 11 个token)。(与代码写法有一定关系，本代码将空的提示词设为空的字符串提交)';
                        }
                        notif_cn = '发送失败，GPT记忆（上下文长度）超过上限。关闭通知后，您可以选择：修改发送内容长度、撤销聊天记录等操作来继续之前的对话。';
                        const autoMaxTokens1_cn = '\r\n\r\n★ 已为您关闭"智能max_tokens"功能，请根据下方的错误提示信息来修改发送内容的长度 (或通过 [撤销]、[清除记忆] 来释放tokens)，然后手动调节 [API设置] 中的 [max_tokens] 。待问题解决后，下次可尝试重新打开该功能。'
                        const autoMaxTokens2_cn = '\r\n\r\n★ 请根据下方的错误提示信息来修改发送内容的长度 (或通过 [撤销]、[清除记忆] 来释放tokens)，然后手动调节 [API设置] 中的 [max_tokens] 。注意: 当前无法使用"智能max_tokens"功能，当您解决"内容超量"问题后，可尝试重新打开"智能max_tokens"功能。'
                        notif_cn = this.openSmartMaxTokens ? notif_cn + autoMaxTokens1_cn : notif_cn + autoMaxTokens2_cn;
                        notif_cn = notif_cn + tips_cn;

                        tips_en += '\r\n\r\n';
                        tips_en += '  • "messages" :  The current length of the context (including the last message sent).';
                        if (!this.isSimpleMode) {
                            // The tokens for sending messages are higher than the actual count, which is probably because OpenAI adds hidden prompt words. After testing, 1) when the "system prompt"  is included in the context of this.msgContent in the code (this.msgContent[0]), even if the system prompt is empty, the first message will have an extra 13 tokens; 2) each subsequent message will take up an additional 10 tokens.
                            tips_en += '\r\n  • Official behavior of OpenAI: The first message will occupy an additional 13 tokens, and each subsequent message will add 10 additional tokens (sending content with 1 token will actually occupy 11 tokens). (It is related to the code writing style, when the system prompt is empty, this code submits an empty string to OpenAI.)';
                        }
                        notif_en = "Sending failed. The length of the context exceeds the limit. After closing the notification, you can choose to modify the content, or undo to continue.";
                        const autoMaxTokens1_en = '\r\n\r\n★ The "Smart max_tokens" feature has been turned off for you. Please make adjustments (such as modifying the content length) based on the [OpenAI - API Error Message] below, and then manually adjust [API Settings] > [max_tokens] for this request. Once the issue is resolved, you can re-enable this feature for the next request.'
                        const autoMaxTokens2_en = '\r\n\r\n★ Please make adjustments (such as modifying the content length) based on the [OpenAI - API Error Message] below, and then manually adjust [API Settings] > [max_tokens] for this request. (The "Smart max_tokens" feature is currently unavailable. You can try re-enabling it once you have resolved the "content exceeds limit" issue).'
                        notif_en = this.openSmartMaxTokens ? notif_en + autoMaxTokens1_en : notif_en + autoMaxTokens2_en;
                        notif_en = notif_en + tips_en;

                        title = this.settingsLanguage == 'cn' ? '错误 - 400 记忆达到上限' : 'Error - 400 Context length exceeds the limit';

                        //Smart max_tokens/智能max_tokens开启时。弹窗，两个，加一个底部提示，延迟显示报错弹窗
                        if (this.isSendingNow_ForSmartMaxTokens && this.openSmartMaxTokens) {
                            delaytime = 1400; //设置报错弹窗延迟1.4秒弹出
                            const notif_bottom = this.settingsLanguage == 'cn' ? '已关闭 智能max_tokens' : 'Smart max_tokens Disabled';
                            this.showNotification(notif_bottom, 'bottom');
                        }
                        //关闭Smart max_tokens
                        this.openSmartMaxTokens = false;
                        this.isReminded_SmartMaxTokens = 2;//下下次提醒

                    } else if (maxLength != null && b_requestedLength != null && b_messagesLength != null && b_completionLength != null) {
                        //发送出的上下文  + max_tokens > 超限制
                        tips_cn += '\r\n\r\n';
                        tips_cn += '#A = 当前模型支持的最大上下文长度：' + maxLength + ' tokens\r\n';
                        tips_cn += '#B = 您当前发送的上下文长度：' + b_messagesLength + ' tokens (GPT的记忆，含本次发送内容)\r\n';
                        tips_cn += '#C = 当前设置允许GPT回复的最大长度：' + b_completionLength + ' tokens (API设置：max_tokens)\r\n';
                        tips_cn += '#D = B + C ，当前 D = ' + b_requestedLength + ' tokens\r\n';
                        tips_cn += '规则：D < ' + maxLength + ' \r\n';
                        tips_cn += '当前：D > ' + maxLength + ' \r\n';
                        tips_cn += '解释：总量 A 有限制，当记忆 B 增加到某个程度时， C (max_tokens) 就需要动态下调\r\n';
                        const diffC = parseInt(maxLength) - parseInt(b_messagesLength);
                        tips_cn += '★ 调整：如果不修改发送内容，当前 A - B = ' + diffC + '，所以必须调整API设置中的"max_tokens"，最大为 ' + (diffC - 1) + '，然后重问即可';
                        if (!this.isSimpleMode) {
                            tips_cn += '\r\n\r\n  • token：单位，即"令牌"';
                            tips_cn += '\r\n  • 当前 GPT 模型 (' + model_data.modelname + ') 最高支持 ' + model_data.maxtokens + '/' + (model_data.maxtokens + 1) + ' tokens';
                            //completion_tokens(本次提问内容的token数)需要等下次对话结束才能计算得出（提问的token数会比实际的高，疑似官方会添加隐藏的提示词。意义大不，仅做参考）经过测试，1、当this.msgContent包含系统提示词这一项时（[0]，即使为空），首条会额外占用 13 token ； 2、之后每条发送的信息额外占用 10 token 。
                            tips_cn += '\r\n  • OpenAI官方行为：首条消息额外占用 13 个token，之后的每条消息都会额外加 10 个token (发送只有 1 个token的内容，实际上会占用 11 个token)。(与代码写法有一定关系，本代码将空的提示词设为空的字符串提交)';
                        }
                        notif_cn = '发送失败，GPT记忆（上下文长度）即将超限。关闭通知后，您可以选择：调整max_tokens后点击重问、撤销聊天记录、修改发送内容长度等操作来继续之前的对话。';
                        const autoMaxTokens1_cn = '\r\n\r\n★ 已为您关闭"智能max_tokens"功能，本次请根据下方的错误提示信息来手动调节 [API设置] 中的 [max_tokens] 。待问题解决后，下次可尝试重新打开该功能。'
                        const autoMaxTokens2_cn = '\r\n\r\n★ 请根据下方的错误提示信息来手动调节 [API设置] 中的 [max_tokens] 。\r\n★ 提醒: 当前未启用"智能max_tokens"功能，建议您打开"智能max_tokens"功能，当其无法使用而自动关闭时，再尝试手动调节 max_tokens。'
                        notif_cn = this.openSmartMaxTokens ? notif_cn + autoMaxTokens1_cn : notif_cn + autoMaxTokens2_cn;
                        notif_cn = notif_cn + tips_cn;

                        tips_en += '\r\n\r\n';
                        tips_en += '  • "messages" :  The current length of the context (including the last message sent).\r\n';
                        tips_en += '  • "completion" :  The API parameter "max_tokens" (adjustable in the API settings).\r\n';
                        tips_en += "  •  Tip:  Here's an example, if you don't want to modify the content being sent: Currently " + maxLength + ' - ' + b_messagesLength + ' = ' + diffC + ', so you need to adjust the "max_tokens" in [API Settings] to be less than ' + diffC + ' ( ★ maximum value is ' + (diffC - 1) + '), then click [Retry] to resend the request.';
                        if (!this.isSimpleMode) {
                            // The tokens for sending messages are higher than the actual count, which is probably because OpenAI adds hidden prompt words. After testing, 1) when the "system prompt"  is included in the context of this.msgContent in the code (this.msgContent[0]), even if the system prompt is empty, the first message will have an extra 13 tokens; 2) each subsequent message will take up an additional 10 tokens.
                            tips_en += '\r\n  • Official behavior of OpenAI: The first message will occupy an additional 13 tokens, and each subsequent message will add 10 additional tokens (sending content with 1 token will actually occupy 11 tokens). (It is related to the code writing style, when the system prompt is empty, this code submits an empty string to OpenAI.)';
                        }
                        notif_en = "Sending failed. The length of the context is about to exceed the limit. After closing the notification, you can choose to retry after modifying the max_tokens, undo, or modify the content to continue.";
                        const autoMaxTokens1_en = '\r\n\r\n★ The "Smart max_tokens" feature has been turned off for you. Please manually adjust [API Settings] > [max_tokens] based on the [OpenAI - API Error Message] below. Once the issue is resolved, you can re-enable this feature for the next request.'
                        const autoMaxTokens2_en = '\r\n\r\n★ Please manually adjust [API Settings] > [max_tokens] based on the [OpenAI - API Error Message] below. \r\n★ Reminder: The "Smart max_tokens" feature is currently disabled. It is recommended to enable the "Smart max_tokens" feature, and only manually adjust max_tokens when it stops automatically.'
                        notif_en = this.openSmartMaxTokens ? notif_en + autoMaxTokens1_en : notif_en + autoMaxTokens2_en;
                        notif_en = notif_en + tips_en;

                        title = this.settingsLanguage == 'cn' ? '错误 - 400 记忆即将达到上限' : 'Error - 400 Context length is about to exceed the limit';

                        // 智能max_tokens（Smart max_tokens）开启时，因max_tokens计算错误而导致报错后，将自动修正max_tokens，并自动重问(Retry)
                        // (问题已解决，轮询 / 自动轮换API Key / Auto Key Rotation 功能已出)*免费GPT账号1分钟只能发3次请求，出错后自动重发，等于是连续发了2个请求，比较容易报错（Err： 429-1 请求过快 Rate limit ），这个无法解决。（问题已解决）
                        if (this.isSendingNow_ForSmartMaxTokens && this.openSmartMaxTokens && (diffC - 1) > 0) { 	// 正在发送 && 已开启"Smart max_tokens" && max_tokens >= 1时(也可以更高)
                            //自动修正max_tokens并自动重问时，无需关闭this.openSmartMaxTokens，继续保持启用状态

                            this.isFixingMaxTokens_SmartMaxTokens = true; //标记为“正在修复MaxTokens”的状态，本次自动重问(Retry)时，不再自动调节max_tokens。
                            //this.isFixingMaxTokens_SmartMaxTokens 什么时候变为false？ 1、重问（retry）成功，得到回复后需要变为false。2、自动重问失败后，用户不再选择重问，而是选择发送新内容时，需要变为false。(覆盖 发送、清空后发送、撤销后发送 三种情况)

                            this.apiMaxTokens = diffC - 1; //精准调节max_tokens的最大值

                            const txt_lang1 = this.settingsLanguage == 'cn' ? '#### #自动修复错误中，即将自动重问\r\n\r\n' : '#### #Auto-correcting error, will automatically retry\r\n\r\n';
                            this.chathistory += (txt_lang1);

                            this.isFirstRetry_SmartMaxTokens = true;
                            this.retry(); // 重问 Retry

                            notif_cn = "max_tokens过高，已自动修复，并自动重问一次";
                            notif_en = "max_tokens was set too high, it has been automatically corrected and the request is now being automatically resent.";
                            notif = this.settingsLanguage == 'cn' ? notif_cn : notif_en;
                            const notiftime = this.settingsLanguage == 'cn' ? 4500 : 6000;
                            this.showNotification(notif, 'bottom', '', notiftime);
                            //返回 * 这里返回前不用改变isSendingNow_ForSmartMaxTokens，因为重问后，成功或失败都会更改它的值
                            return;
                        } else {
                            //Smart max_tokens开启时。弹窗，两个，加一个底部提示，延迟显示报错弹窗
                            if (this.isSendingNow_ForSmartMaxTokens && this.openSmartMaxTokens) {
                                delaytime = 1400; //设置报错弹窗延迟1.4秒弹出
                                const notif_bottom = this.settingsLanguage == 'cn' ? '已关闭 智能max_tokens' : 'Smart max_tokens Disabled';
                                this.showNotification(notif_bottom, 'bottom');
                            }

                            //关闭Smart max_tokens
                            this.openSmartMaxTokens = false;
                            this.isReminded_SmartMaxTokens = 2;//下下次提醒
                        }

                    } else {
                        notif_cn = '发送失败，GPT记忆（上下文长度）达上限。';
                        notif_en = "Sending failed. The length of the context exceeds the limit. ";
                        title = this.settingsLanguage == 'cn' ? '错误 - 400 ? ' : 'Error - 400 ? ';
                    }

                    apidata_errorState = '400';//用于更新this.apiCheckedData

                } else {
                    //未知情况  other ？
                    notif_cn = 'HTTP请求 - 错误代码:  ' + error.code + '\r\nHTTP请求 - 错误信息:  ' + error.message;
                    notif_en = 'HTTP Error Code:  ' + error.code + '\r\nHTTP Error Message:  ' + error.message;
                    title = this.settingsLanguage == 'cn' ? '错误 - ?' : 'Error - ?';
                    apidata_errorState = 'error-other';//用于更新this.apiCheckedData
                }


                if (responseMessage != '' || responseType != '') {
                    notif_cn = notif_cn + '\r\n\r\nOpenAI - API错误信息:\r\n' + responseMessage + '\r\nOpenAI - API错误类型:\r\n' + responseType + '\r\nOpenAI - 服务器状态:\r\nhttps://status.openai.com/' + '\r\nOpenAI - 费率限制规则 (含免费):\r\nhttps://platform.openai.com/docs/guides/rate-limits/overview';
                    notif_en = notif_en + '\r\n\r\nOpenAI - API Error Message:\r\n' + responseMessage + '\r\nOpenAI - API Error Type:\r\n' + responseType + '\r\nOpenAI - OpenAI Status:\r\nhttps://status.openai.com/' + '\r\nOpenAI - Rate limits:\r\nhttps://platform.openai.com/docs/guides/rate-limits/overview';
                }
                notif = this.settingsLanguage == 'cn' ? notif_cn : notif_en;
                const autoRetry_bottomnotif = this.settingsLanguage == 'cn' ? autoRetry_bottomnotifCN : autoRetry_bottomnotifEN;


                const temp_isSendingNow_ForSmartMaxTokens = this.isSendingNow_ForSmartMaxTokens; //临时记录，目的是保持this.isSendingNow_ForSmartMaxTokens的位置不动，不需要移动到代码末尾（先不移动，避免出错）
                //更新发送状态
                this.isSendingNow_ForSmartMaxTokens = false;//暂时先不移动到“自动重问代码”的后面

                //更新this.apiCheckedData  记录已验证的API Key信息
                this.updateApiCheckedData(this.api, requesttime, apidata_errorState);

                //Send(need auto-retry)  发送失败后需要自动重问/重发消息的情况   Automatically Retry (resend messages)
                //借用isSendingNow_ForSmartMaxTokens判断：如果为true，表示当前是[发送]时出错,而不是[验证API Key]时出错。 [Check API Key]出错时不需要Retry。
                if (temp_isSendingNow_ForSmartMaxTokens && autoRetry_errortype !== '') {
                    this.autoRetry(autoRetry_errortype, autoRetry_bottomnotif, notif, title);
                } else {
                    //Send or Check  发送失败后无需重问的情况 或者 验证失败后
                    //tips：延迟用途1：针对'Type error'错误，本地验证报错速度快，底部通知“验证中...”刚弹出就报错，会导致底部通知的计时器来不及关闭，计时器会继续运行，60秒结束后会关闭通知（此时若有通知，会莫名其妙被关闭）。
                    //tips：延迟用途2：400时，Smart max_tokens失效出错，此时底部弹出提示“已自动修复 并自动重问”，此时连续两次请求（出错+立马重问）很容易触发“429-1 请求过快 Rate limit ”弹出报错信息。 所以，这种情况下，429需要延迟弹出。
                    setTimeout(() => {
                        this.showNotification(notif, 'center', title);
                    }, delaytime);
                }
            },

            //自动重问/重发消息  Automatically Retry (resend messages)  - 轮询 / 自动轮换API Key / Auto Key Rotation 相关
            autoRetry(errortype, bottomnotif, centernotif, centernotiftitle) {
                //catchApiErrorCode()
                //Error Code  ErrorType
                //1 no  'Type error' *非法格式的key，不重发
                //2 yes *1次 提醒检查网络 'timeout exceeded'
                //3 yes *1次 提醒检查网络 'Network Error'
                //4 no  '401' 密钥无效 API Key Error   *无此key，包括key已被删等情况，不重发
                //5 yes *3～5次 429-1 请求过快 Rate limit  *包括 429-1-day
                //6 no  429-2 超出配额 Insufficient quota   *key出问题，建议不重发
                //7 yes *2次 429-3 服务器过载 Servers overloaded
                //8 yes *2次 429-4 模型超载  Model overloaded  tips: responseType = server_error
                //9 no   429-other 未知情况 *保留错误信息 不重发
                //10 no  404 当前账号不属于任何组织   *key出问题，最好不重发
                //11 yes *2次 500 服务器问题 Server error
                //12 yes *2次 503 503-1 503-2 服务器问题 超载或错误
                //14 no  400 超过max_tokens上限  “智能max_tokens”会处理&重发
                //15 no  other 其他未知情况   *保留错误信息 不重发
                //new 16 yes  429-5 tokens请求过快(每分钟)  Rate limit reached for tokens(per min)
                //new 17 yes 401 > FirstCheckedKey_401 密钥无效 API Key Error
                //new 18 yes 429-2 > FirstCheckedKey_429-2  超出配额 Insufficient quota
                //new 19 yes 404 > FirstCheckedKey_404 当前账号不属于任何组织

                //Need Auto Retry： 2、3、5、7、8、11、12、16

                if (this.autoRetry_FirstTime === 0) {
                    this.autoRetry_FirstTime = new Date().getTime();//首次
                }
                const time_now = new Date().getTime();
                const time_interval = time_now - this.autoRetry_FirstTime;//距首次自动重问的间隔
                let notiftype = '';
                let delaytime = 0;//延迟重问

                //测试 console.log('首次自动重问的时间： ' + new Date(this.autoRetry_FirstTime).toLocaleString());
                //测试 console.log(time_interval);
                //测试 console.log('排除列表: ');
                //测试 console.log(this.autoRetry_apiKeyExcludeList);
                // 排除列表 - 将api key添加到排除列表中
                // *目前 2、3、5、7、8、11、12、16中，应该只有第5、16明确是暂时无法使用的key，需要排除。 *无需排除的key，即虽然本次出错了，但可马上重试的key
                // * v7.20 Add 17、18、19，这3种都需要排除。
                if (errortype === '429-1' || errortype === '429-5' || errortype === 'FirstCheckedKey_401' || errortype === 'FirstCheckedKey_429-2' || errortype === 'FirstCheckedKey_404') {
                    const existingItem = this.autoRetry_apiKeyExcludeList.find(item => item.apikey === this.api);
                    if (!existingItem) {
                        // 如果不存在，则添加新内容
                        const newItem = {
                            apikey: this.api,
                            time: time_now
                        };
                        this.autoRetry_apiKeyExcludeList.push(newItem);
                    } else {
                        // 如果存在，则更新时间
                        existingItem.time = time_now;
                    }
                }


                // 排除列表 - 每次都检测排除列表，只保留20秒以内的，超出20秒就允许尝试使用。
                // *主要考虑完善逻辑性，虽然截止7.13自动重问的"次数很少"及“时间间隔较短”
                this.autoRetry_apiKeyExcludeList = this.autoRetry_apiKeyExcludeList.filter(item => {
                    return (time_now - item.time) <= 20000; // 判断时间戳是否小于当前时间20秒
                });


                // 剩余可尝试的Key(剩余最大重试次数)，先过滤掉排除列表中的key，再排除level8和level9(配额不足/过期的key)。
                //*动态，autoRetry_apiKeyExcludeList的数量同时存在增加 和 减少的可能。
                //*目前代码的逻辑（可能会出现变化）：轮换key时（getFirstAvailableAPIKey），是有小概率获取到8或9的。 而这里只是在次数上进行排除。  tip：如何重新检测level8和level9 ？ 1)正常发送时，轮换到8或9  2)开发管理功能、一键检测功能
                let temp_apiCheckedData = this.apiCheckedData.filter(apidata => !this.autoRetry_apiKeyExcludeList.some(obj => obj.apikey === apidata.apikey) && apidata.level !== 8 && apidata.level !== 9);
                let remainingKeysCount = temp_apiCheckedData.length;

                //测试 console.log('剩余可尝试的Key(剩余最大重试次数) remainingKeysCount: ' + remainingKeysCount);

                if (errortype === 'timeout exceeded' || errortype === 'Network Error') {
                    // A: 2、3
                    if (this.autoRetry_log.includes('timeout exceeded') || this.autoRetry_log.includes('Network Error')) {
                        //判断Log：A类，已经重试过1次，不再重试 *A类默认最多1次
                        notiftype = 'center';
                    } else if (time_interval > 6000) {  //*判断时间有些牵强，简单地当作一个时间限制
                        //无论之前多少次的自动重问组合，合计达6秒 *此类错误：大概率是首次出现，如果是多次类型重试后出现网络问题，就不重试了
                        notiftype = 'center';
                    } else {
                        notiftype = 'bottom';
                    }

                } else if (errortype === '429-1' || errortype === '429-5') {
                    // B: 5
                    const matchCount = (this.autoRetry_log.match(/429-1|429-5/g) || []).length;
                    if (matchCount >= 6 || remainingKeysCount < 1) { //B类默认最多6次，如果可用key为0(剩余最大重试次数)，提前结果
                        //判断Log：B类，已经重试过6次，不再重试
                        notiftype = 'center';
                    } else if (time_interval > 10000) {//*判断时间有些牵强，简单地当作一个时间限制
                        //无论之前多少次的自动重问组合，合计达10秒 *此类错误：大概率网络没问题，服务器问题可以忽略，大概率都是重复同类错误，时间短点，主要针对key是否能用，不能用耗时太久无意义
                        notiftype = 'center';

                        //测试 console.log('无论之前有过多少次的“自动重问”的组合，目前达到了 10 秒。停止自动重问 - ' + errortype);

                    } else {
                        notiftype = 'bottom';
                    }

                    //测试 console.log('日志中出现的次数 matchCount ： ' + matchCount);

                } else if (errortype === '429-3' || errortype === '429-4' || errortype === '500' || errortype === '503') {
                    // C: 7、8、11、12
                    const regex = /(429-3|429-4|500|503)/g;
                    const matchCount = (this.autoRetry_log.match(regex) || []).length;
                    if (matchCount >= 3 || remainingKeysCount < 1) {  //C类默认最多3次，如果可用key为0(剩余最大重试次数)，提前结果
                        // Code to handle when any of the contents appeared at least 3 times
                        //判断Log：C类，已经重试过3次，不再重试
                        notiftype = 'center';
                    } else if (time_interval > 30000) {//*判断时间有些牵强，简单地当作一个时间限制
                        //无论之前多少次的自动重问组合，合计达30秒  *此类错误：大概率网络没问题+key没问题，大概率重复遇到服务器问题，单次失败请求的耗时可能会很久，所以间隔需要长点。
                        notiftype = 'center';
                    } else {
                        notiftype = 'bottom';
                    }
                } else if (errortype === 'FirstCheckedKey_401' || errortype === 'FirstCheckedKey_429-2' || errortype === 'FirstCheckedKey_404') {
                    // D: 17、18、19
                    const regex = /(FirstCheckedKey_401|FirstCheckedKey_429-2|FirstCheckedKey_404)/g;
                    const matchCount = (this.autoRetry_log.match(regex) || []).length;
                    if (matchCount >= 20 || remainingKeysCount < 1) {  //D类默认最多20次，如果可用key为0(剩余最大重试次数)，提前结果
                        //判断Log：D类，已经重试过20次，不再重试
                        notiftype = 'center';
                    } else if (time_interval > 60000) {//*判断时间有些牵强，简单地当作一个时间限制
                        //无论之前多少次的自动重问组合，合计达60秒  *此类错误：导入全新key 或 批量添加key时，对新key进行首次验证，如果大量key存在问题，启动自动重问，连续检测，20次内不停
                        notiftype = 'center';
                    } else {
                        delaytime = 0;//连续请求最多20次，避免请求过快被官方限制，这里可以设置延迟重问(比如:30 *30毫秒) *未发现请求过快会被限制，已取消延迟，设为0，不延迟retry
                        notiftype = 'bottom';
                    }
                } else {
                    notiftype = 'center';
                }


                if (notiftype === 'bottom') { //自动重问，改为底部通知
                    const retryLog = errortype + ' ';//本次简单的日志记录
                    this.autoRetry_log += retryLog;//结束时追加日志记录

                    //测试 console.log('执行重问之前，排除列表中key的数量： ' + this.autoRetry_apiKeyExcludeList.length);

                    setTimeout(() => {
                        this.retry();//重问  *是否有一定要延时的原因？：
                    }, delaytime);//可不延时，可延时0，可延时几十毫秒，再高“按钮可用状态”就会闪
                } else { //notiftype = 'center' 停止重问，中心通知
                    //主要有两个地方需要重置autoRetry的变量数据：1.发送成功时  2.自动重问停止时(即最后一次自动重问失败时)
                    this.autoRetry_apiKeyExcludeList = []; //重置autoRetry数据-1: 清空"自动重问"的 API Key排除列表
                    this.autoRetry_FirstTime = 0; //重置autoRetry数据-2: 清空首次自动重问的时间
                    this.autoRetry_log = ''; //重置autoRetry数据-3: 清空临时日志

                    //测试 console.log('最后一次自动重问（最后一次-失败-停止重问），排除列表中key的数量： ' + this.autoRetry_apiKeyExcludeList.length);
                }


                //底部通知 或 中心通知
                setTimeout(() => {
                    const notif = notiftype === 'center' ? centernotif : bottomnotif;

                    //当前存在通知 & 当前通知在底部 & 本次通知在底部
                    const notif_state = (this.isShowNotification === true && this.setNotificationPosition === 'bottom' && notiftype === 'bottom');
                    //当前通知为自动重问的通知
                    const notif_isAutoRetry = (this.setNotificationContent.includes('自动重问') || this.setNotificationContent.includes('自动重发') || this.setNotificationContent.includes('自动重试') || this.setNotificationContent.toLowerCase().includes('retry') || this.setNotificationContent.toLowerCase().includes('resend'))

                    if (notif_state && notif_isAutoRetry) {
                        //直接替换内容
                        this.setNotificationContent = notif;
                    } else {
                        //底部通知 或 中心通知
                        this.showNotification(notif, notiftype, centernotiftitle, 2300);// 显示时间不要太长，不考虑“偶尔遇到不同错误，通知文字被替换新内容后，来不及看就时间到了 (闪)“的情况
                    }
                }, 80); //考虑showNotification弹出有50ms的延迟，实际这里不延迟好像也没问题
            },

            //聊天记录滚动至最下方。Scroll to the bottom of the chat.
            scrollToBottomView() {
                //Tips： body-Height 以下3个都可以能用
                //document.body.offsetHeight
                //document.body.clientHeight
                //document.body.scrollHeight

                //Tips： screen-Height
                //window.screen.height NO! xxxxxx
                //window.screen.availHeight NO! xxxxxx
                //window.outerheight  NO! xxxxx
                //window.innerHeight  xx 移动端：会跟随双指放大改变；软键盘弹出后会改变。
                //document.documentElement.clientHeight 与window.innerHeight一样，但是不会跟随双指放大而改变，软键盘弹出后也不会改变。

                const positioningDiv = document.querySelector('.bottom-position');
                //加入<!DOCTYPE html>后  问题得到解决
                const bodyheight = document.body.offsetHeight + 8 + 8; //margin-top:8  margin-bottom:8
                const screenheight = document.documentElement.clientHeight;
                if (bodyheight > screenheight) {
                    this.$nextTick(() => {
                        positioningDiv.scrollIntoView({behavior: 'smooth', block: 'start'});
                    });
                }
            },
            //移动端 - 点击输入框 - 聊天记录滚动至最下方。 For Mobile - Click on chat input box -  Scroll to the bottom of the chat.
            scrollToBottomViewClick() {
                //检测是否为移动端 且是否为首次点击
                if (this.isfirstclickonchatinputbox && navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)) {

                    this.isfirstclickonchatinputbox = false;
                    this.scrollToBottomView();
                }
            },
            //关闭软键盘，移动端头几条内容较短的信息不会产生屏幕滚动，这里做关闭软键盘处理，可以看到消息，体验会更好。 close Software Keyboard For Mobile
            closeSoftwareKeyboard() {
                const bodyheight = document.body.offsetHeight - 95 + 8; //bottom-position:95   margin-top:8
                const screenheight = document.documentElement.clientHeight;
                let closeSoftwareKeyboard = false;
                if (navigator.userAgent.match(/(pad|iPad)/i)) {
                    //(平板) 每次发送后关闭软键盘
                    if (bodyheight < screenheight * 3 / 7) {
                        closeSoftwareKeyboard = true;
                    }
                } else if (navigator.userAgent.match(/(phone|pod|iPhone|iPod|ios|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)) {
                    //(手机) 每次发送后关闭软键盘
                    if (bodyheight < screenheight * 5 / 7) {
                        closeSoftwareKeyboard = true;
                    }
                }
                if (closeSoftwareKeyboard) {
                    this.$nextTick(() => {
                        if (document.activeElement && document.activeElement !== document.body) {
                            document.activeElement.blur();
                        }
                    });
                }

            },
            //对话框 电脑端支持组合键换行(Line break)  支持Ctrl+Enter 和 Commond+Enter 和 Win+Enter 换行，另有shift+enter（无需设置）
            newLine(e) {
                //解决safari每次都是换两行的bug，其他浏览器正常   e=ee？
                let ee = window.event || arguments[0];
                ee.returnValue = false;

                // 1.获取光标位置
                const ele = e.target;
                const cursorIndex = ele.selectionStart;
                // 2.光标后加入换行符
                let temp_text = this.msg.split('');
                temp_text.splice(cursorIndex, 0, '\n');
                this.msg = temp_text.join('');
                // 3.移动光标 摘：“移动光标时要注意，因为Vue响应式，在修改了text的值后，如果立刻执行移动光标，则紧接着就会因为重新设置了text的值，光标会移动到最后，所以要等dom操作完毕后，再进行移动光标的操作。”
                Vue.nextTick(() => {
                    ele.selectionStart = ele.selectionEnd = cursorIndex + 1;

                    //自适应高度
                    let height6px = parseInt(ele.scrollHeight) + 6; // +
                    ele.style.height = '38px'; //about：自适应高度 max-height maxHeight
                    //ele.style.maxHeight = ele.scrollHeight + 'px'; // -
                    //ele.style.height = ele.scrollHeight + 'px'; // -
                    ele.style.maxHeight = height6px.toString() + 'px'; // +
                    ele.style.height = height6px.toString() + 'px';  // +
                })
            },
            //对话框 按回车键Enter发送之前需要判断是否为中文输入法  The current input method is in Chinese or not.
            textareaEnter(e) {
                let ee = window.event || arguments[0];


                //电脑端中文输入法时ee.keyCode为229，即中文输入法/中英混输/拼音状态下输入英文时，避免按回车键后直接发送出去
                if (ee.key == "Enter" && (ee.code == "Enter" || ee.code == "NumpadEnter") && ee.keyCode == 13) {
                    //阻止原始控件回车换行的动作
                    ee.returnValue = false;

                    //执行发送
                    this.sendMsg();
                }
            },
            //解决 textarea (Enterkeyhint = send) 输入法中“换行”的兼容性问题 （移动端)
            //当textarea控件中的"Enterkeyhint"设置为"send"时，手机上的输入法会出现"发送Send"按钮（功能正常），此时多数"中文输入法"会提供“换行”功能，而这个换行键很可能存在兼容性问题。
            //*针对部分机型 部分输入法版本 *v7.21 百度的换行问题已解决；搜狗只能勉强换行（会多出一行）；
            textareaMobileInputCompatibility(e) {
                //'\r'回车，'\n'换行
                //Windows 回车换行符：\r\n
                //Unix、 Mac OS 9之后 回车换行符：\n  之前是：\r
                //iphone：百度输入法的“换行键”无法换行，且会退格删除；搜狗输入法的“换行键”无法换行，且会添加隐形回车符号； 微信输入法 与 讯飞输入法 则可以成功换行。
                //测e.key结果：百度换行会执行"\n\u007" + 1次Backspace(退格键)；搜狗换行会执行"\r\r" + 1次Backspace(退格键) + 搜狗会动态计算替换多个\r或\n；
                //测e.key结果：微信输入法换行会执行"\n "(还有个换行符) + 1次Backspace(退格键)；讯飞换行会执行"\n\r" + 1次Backspace(退格键)；

                // *仅在iPhone中测试
                try {
                    //测试test this.gptSystemPrompt += '\r\n--------------'; //test
                    //测试test this.gptSystemPrompt += ('\r\nStart - e.key:' + JSON.stringify(e.key) + '_  e.code:' + e.code + '_  e.keyCode:' + e.keyCode );

                    if (e.key === "\n\u0007" && e.code === 'Unidentified' && e.keyCode === 0) {
                        //百度输入法 换行键keydown事件的特定特征（3条件同时成立）
                        e.preventDefault();// 阻止 默认的 "\n\u0007" 输出行为
                        //百度“换行”实际上是连续两次键入，第1次是键入"\n\u0007" ，第2次是键入“Backspace”（删除键）。 "\n\u0007"并没有实际占用字符(两个字符?)，所以第2次自动执行“Backspace”时会删除用户输入的内容
                        this.newLine(e); //借用newLine，刚好可以适用，实现换行
                        this.bugfixing_BaiduInput_NewLine_timestamp = new Date().getTime();//当前时间
                        //还需要阻止第2次自动键入“Backspace”
                        //测试test this.gptSystemPrompt += ('\r\n百度 - e.key:' + JSON.stringify(e.key) + '_  e.code:' + e.code + '_  e.keyCode:' + e.keyCode );

                    } else if (e.key === "\r\r" && e.code === 'Unidentified' && e.keyCode === 229) {
                        //搜狗输入法 换行键keydown事件的特定特征（3条件同时成立）
                        e.preventDefault();// 阻止 默认的 "\r\r" 输出行为
                        //搜狗“换行”实际上是连续两次键入，第1次是键入"\r\r" ，第2次是键入“Backspace”（删除键）。 "\r\r"没有实际作用，且是隐藏的，并占位（换行几次就占位几次，光标不变），搜狗第2次自动执行“Backspace”时没有任何变化
                        //测试test this.gptSystemPrompt += ('\r\n1...' + JSON.stringify(this.msg) );
                        this.newLine(e);
                        //不需要阻止第2次自动键入“Backspace” //与百度不同的原因是，搜狗是动态处理，找不到规律，搞不定...
                        //测试test this.gptSystemPrompt += ('\r\n搜狗 - e.key:' + JSON.stringify(e.key) + '_  e.code:' + e.code + '_  e.keyCode:' + e.keyCode );
                        //测试test this.gptSystemPrompt += ('\r\n2...' + JSON.stringify(this.msg) );

                    } else if (e.key === "Backspace" || e.code === 'Backspace') {
                        //判断Backspace是否由百度输入法自动发出的
                        const time_diff = new Date().getTime() - this.bugfixing_BaiduInput_NewLine_timestamp;
                        //测试test this.gptSystemPrompt += ('\r\ndiff...' + time_diff );
                        //如果时间差在100毫秒以内，则判定为百度自动发出的删除键，需要忽略掉，不执行。
                        if (time_diff < 100) {
                            e.preventDefault();
                        }
                        //测试test this.gptSystemPrompt += ('\r\n删除键 - e.key:' + JSON.stringify(e.key) + '_  e.code:' + e.code + '_  e.keyCode:' + e.keyCode );
                        //测试test this.gptSystemPrompt += ('\r\n3...' + JSON.stringify(this.msg) );
                    }

                } catch (err) {
                }

            },


            //按钮：发送  Send
            sendMsg() {

                debugger;

                if (this.webSocket.readyState != 1) {
                    this.sentext = "网络错误";
                    let notif = this.settingsLanguage == 'cn' ? '当前连接已失效，请重新连接' : 'The current connection has expired, please reconnect';
                    this.showNotification(notif, 'bottom', '', 5000);
                    return;
                }


                if (window.marked == null) {
                    alert("marked.js（Markdown插件）尚未加载成功，请数秒后再尝试...\r\nmarked.js (Markdown Plugin) has not been loaded successfully, please try again in a few seconds...");
                    return;
                }

                let isRetry = false; //**是否重问，默认false - 正常发问
                let sendMessage = ''; //**需要发送的问题，初始值为空
                if (this.isRetry_RetryMessage != "") {
                    isRetry = true; //当前是重问状态
                    sendMessage = this.isRetry_RetryMessage; //数据转移

                }

                //**重置，不再使用isRetry_RetryMessage，减少出错风险。 Reset, stop using isRetry_RetryMessage to reduce the risk of errors.
                this.isRetry_RetryMessage = '';

                // **如果不是[重问]，是在[正常发问] - 那么需要判断this.msg是否为空
                if (!isRetry && this.msg.trim() == "") {
                    let notif = this.settingsLanguage == 'cn' ? '无法发送空白消息' : 'Unable to send blank messages';
                    let notiftime = 1500;
                    this.showNotification(notif, 'bottom', '', notiftime);
                    return;
                }

                this.isSendingNow = true; //更新发送的状态，之后不能再出现"return"代码
                this.isSendingNow_ForSmartMaxTokens = true; //同步更新发送的状态
                this.btnDisabledState_ApiURL = true;
                this.btnDisabledState_Language = true;
                this.btnDisabledState_ContextualMode = true;


                //[length - 1]["my"] = ture :说明最后一条是失败记录，属于my/user，且现在点击的是发送，而不可能是重问。（重问是先将list.pop，之后再模拟发送）
                if (this.msgList[this.msgList.length - 1]["my"]) {
                    if (this.msgContentForMsgList_SingleRound[this.msgContentForMsgList_SingleRound.length - 1]["content"] != this.msg) {
                        this.userMsgTokensForRetry = 0; //重问失败后，提问内容发生改变时(即不再重问时)，userMsgTokensForRetry需要归0。
                    }
                }

                // this.beginLoadingAnimation();
                // document.querySelector('.inputapikey').readOnly = true; //发送中，apikey禁止改动

                //**正常发问- sendMessage需要替换为this.msg
                if (!isRetry) {
                    sendMessage = this.msg;
                    this.isFixingMaxTokens_SmartMaxTokens = false; //取消“正在修复MaxTokens”的状态。 当Smart Tokens遇到错误，会自动修复并自动重问(Retry)，自动重问失败后，用户不再选择重问，而是选择发送新内容时，需要变为false。(这里覆盖 发送、清空后发送、撤销后发送 三种情况)
                    //以下3行代码是多余的，可省略。 只要有另外两处代码即可：1.发送成功时 2.自动重问停止时(即最后一次自动重问失败时)
                    this.autoRetry_apiKeyExcludeList = []; //重置autoRetry数据-1: 清空"自动重问"的 API Key排除列表
                    this.autoRetry_FirstTime = 0; //重置autoRetry数据-2: 清空首次自动重问的时间
                    this.autoRetry_log = ''; //重置autoRetry数据-3: 清空临时日志
                }

                let userMsgMarkdown = marked.parse(sendMessage); // marked.js + highlight.js  Markdown+高亮处理
                if (sendMessage.trim() == "" || sendMessage.replace(/^\n|\n$/g, "").trim() == "") {
                    userMsgMarkdown = "<p> </p>"; //*目前“发送内容”sendMessage不允许为空，不会执行到这里
                } else if (userMsgMarkdown.trim() == "") {
                    userMsgMarkdown = "<p>" + sendMessage + "</p>"; //v5.16 BUG A ： 问题不为空，但marked处理后变成空，则userMsgMarkdown重置为未经过marked处理的原文
                }
                if (userMsgMarkdown.trim() == "") {
                    userMsgMarkdown = "<p> </p>"; // v5.16 BUG B ： 若问题为空，则输出加了<p>标签的空格，撑起高度，保持正确的气泡框样式
                }

                const userMsgDatetimeNow = this.formatDateYYYYMMDDHHmmss(new Date(), ':');

                //准备微信样式时间
                let wechatstyledatetime = null; //默认为空
                if (this.msgList[this.msgList.length - 1]["hr"] === true) { //前面不存在分割线
                    wechatstyledatetime = userMsgDatetimeNow.slice(5, 16);
                } else {
                    if (this.displayDatetime_WeChatStyle() != null) { //前面无分割线时：判断聊天记录的时间跨度是否超过5分钟
                        //微信样式的时间  时间格式会转换为MMDDHHmm
                        wechatstyledatetime = userMsgDatetimeNow.slice(5, 16);
                    }
                }

                // this.msgList.push this.msgContentForMsgList_SingleRound.push

                // 发送和显示的消息体
                let wsMsg = {
                    "msg": userMsgMarkdown,
                    "my": true,     // 是否是我发送消息，用作判断消息左右显示
                    "prompt": false,     // 是否是系统消息
                    "datetime": userMsgDatetimeNow,
                    "wechatstyledatetime": wechatstyledatetime, //null 或 userMsgDatetimeNow
                    "fromUserID": "",
                    "toUserId": "",
                    "msgType": "3",
                }

                if (this.webSocket.readyState === 1) {
                    // 也没显示
                    this.msgList.push(wsMsg)
                    this.webSocket.send(JSON.stringify(wsMsg));
                } else {

                }

                this.isRetry_RetryMessage = ''; //先重置

                //渲染 usermsg  6.01:发现vue有个“v-html” 绑定后可以实时渲染。算了，暂时不改。 6.19:已改为v-html
                this.$nextTick(() => {
                    const divs = document.querySelectorAll('div.usermsg');
                    const lastDiv = divs[divs.length - 1];

                    // lastDiv.innerHTML = userMsgMarkdown; // 6.19:改为v-html后，不再需要使用innerHTML

                    //判断是否需要关闭软键盘
                    this.closeSoftwareKeyboard();

                    //滚动到最底部
                    this.scrollToBottomView();

                    //一键复制按钮。搜索div内容，找到符合条件的代码块，添加按钮。 addCopyButton.  Copying code to the clipboard
                    this.addCopyButtonToPreTags(lastDiv);

                    //一键复制按钮。为聊天气泡框添加一键复制按钮。 addCopyButton.  Copying Chat Message to the clipboard
                    this.addCopyButtonToChatMessage(lastDiv.parentElement);

                });

                // 发送之后
                if (this.chatcreatedtime == null) {
                    this.chatcreatedtime = userMsgDatetimeNow; //对话创建时间 = 首次发送时间，无论是否成功发送
                    //即时更新List
                    let chatdata_temp = this.chatDataList.find(c => c.chatcreatedtime === null);
                    chatdata_temp = chatdata_temp == null ? {} : chatdata_temp;
                    chatdata_temp.chatcreatedtime = this.chatcreatedtime;
                    //更新selectedChatData  即更新列表“被选中”状态
                    this.selectedChatData = JSON.parse(JSON.stringify(chatdata_temp));//防止双向绑定
                }
                //更新最后活动时间 *这里不保存
                this.chatlasttime = userMsgDatetimeNow; //最后活动时间 ，无论发送是否成功。

                const currentTime = new Date().getTime(); // 获取当前时间

                if (true) {
                    // 将发送消息内容置空
                    this.msg = "";
                    const textarea_Chat = document.querySelector('.textareachatinputbox');
                    textarea_Chat.style.maxHeight = '38px'; //about：自适应高度 max-height maxHeight
                    textarea_Chat.style.borderRadius = '0px 6px 6px 0px'; //border-radius: 0px 6px 6px 0px;
                    textarea_Chat.style.zIndex = "900000"; //z-index:900000 输入框获得焦点后会发光，需要设置按钮与输入框的层级关系

                    this.isSendingNow = false; //更新发送状态
                    this.isSendingNow_ForSmartMaxTokens = false; //同步更新发送状态
                    this.endLoadingAnimation();
                    document.querySelector('.inputapikey').readOnly = false; //发送结束，apikey恢复可改
                    this.isFixingMaxTokens_SmartMaxTokens = false; //取消“正在修复MaxTokens”的状态（当Smart Tokens遇到错误，会自动修复并自动重问(Retry)，当重问成功后，需要重置状态）
                    this.autoRetry_FirstTime = 0; //重置autoRetry数据-2: 清空首次自动重问的时间
                    this.autoRetry_log = ''; //重置autoRetry数据-3: 清空临时日志
                    const aiMsgDatetimeNow = this.formatDateYYYYMMDDHHmmss(new Date(), ':');

                    let txt_lang2;
                    if (this.gptSystemPrompt_hidden.trim() != "") {
                        //若存在隐形的System设置，在聊天记录中做个标记
                        txt_lang2 = this.settingsLanguage == 'cn' ? '**GPT::Hidden** _' : '**GPT::Hidden** _';
                    } else {
                        txt_lang2 = this.settingsLanguage == 'cn' ? '**GPT** _' : '**GPT** _';
                    }
                    this.btnDisabledState_Sending = false;
                    this.btnDisabledState_Clear = false;
                    this.btnDisabledState_Undo = false;
                    this.btnDisabledState_ApiURL = false;
                    this.btnDisabledState_Language = false;
                    this.btnDisabledState_ContextualMode = false;

                    this.btnDisabledState_Export = false;
                    this.sentext = this.settingsLanguage == 'cn' ? '发送' : 'Send';
                    this.userMsgTokensForRetry = 0;


                    //更新 保存 聊天数据 chatData chatDataList
                    //更新最后活动时间
                    this.updateChatLastTime(aiMsgDatetimeNow);//最后活动时间

                    //更新this.apiCheckedData  记录已验证的API Key信息
                    this.updateApiCheckedData(this.api, currentTime, 'success');

                }
            },

            //更新最后活动时间 + 保存   调用：1保存提示词 2撤销 3清空记忆 4发送成功 5发送失败
            updateChatLastTime(lasttime) {
                //更新 保存 聊天数据 chatData chatDataList
                //更新最后活动时间
                if (lasttime !== null) { // *lasttime等于null的情况：发送失败时，不设置时间
                    this.chatlasttime = lasttime;
                }
                //即时更新chatDataList的最近活动时间  *更新List的目的：考虑本次保存有失败的可能(遇到冲突)
                let chatdata_temp = this.chatDataList.find(c => c.chatcreatedtime === this.chatcreatedtime);
                chatdata_temp = chatdata_temp == null ? {} : chatdata_temp;
                chatdata_temp.chatlasttime = this.chatlasttime;
                //保存  *加个延时，以防万一
                setTimeout(() => {
                    this.localStorage_SaveData(null, 'chatDataList');
                    //禁用本地存储时的保存
                    if (!this.isLocalStorageSupported || !this.isLocalStorageAllowed) {
                        //chatdata_temp已经更新了。  this.saveChatData_NoLocalStorage(null);
                    }
                    //列表回到顶部
                    this.gotoTop_List(); //*列表窗口隐藏时无法回到顶部，所以这里是失效的。
                }, 0);
            },

            //聊天输入框失去焦点时
            textareaChatInputBox_blur() {
                this.isfirstclickonchatinputbox = true; //is first click on chatinputbox
                let textarea_Chat = document.querySelector('.textareachatinputbox');
                textarea_Chat.style.minHeight = '38px';
                textarea_Chat.style.height = '38px';
                textarea_Chat.style.borderRadius = '0px 6px 6px 0px'; //border-radius: 0px 6px 6px 0px;
                textarea_Chat.style.zIndex = "900000"; //z-index:900000
            },
            //聊天输入框获取焦点时
            textareaChatInputBox_focus() {
                let textarea_Chat = document.querySelector('.textareachatinputbox');
                textarea_Chat.style.minHeight = '38px';
                //max-height当变量用，存储自适应高度
                textarea_Chat.style.height = textarea_Chat.style.maxHeight;
                textarea_Chat.style.zIndex = "900002"; //z-index:900002 输入框获得焦点后会发光，需要设置按钮与输入框的层级关系

                if (parseInt(textarea_Chat.style.height) > 38) {
                    textarea_Chat.style.borderRadius = '6px 6px 6px 0px'; //border-radius: 6px 6px 6px 0px;
                } else {
                    textarea_Chat.style.borderRadius = '0px 6px 6px 0px'; //border-radius: 0px 6px 6px 0px;
                }
            },
            //API Key输入框失去焦点时
            inputapiblur() {
                this.api = this.api.trim();
                document.querySelector('.inputapikey').type = "password";
            },
            //API Key输入框获取焦点时
            inputapifocus() {
                document.querySelector('.inputapikey').type = "text";
            },
            //按钮状态（key可用 和 key不可用）
            changeButtonDisabledState(isDisabled) {
                debugger;
                if (isDisabled === false) {
                    //启用 验证通过 可发送

                    this.btnDisabledState_CheckAPI = true;
                    this.btnDisabledState_Sending = false;
                    //恢复按钮的准确状态
                    if (this.msgContent.length > 1) {
                        //清空-恢复可用 撤销&重问-恢复可用
                        this.btnDisabledState_Clear = false;
                        this.btnDisabledState_Undo = false;
                    } else if (this.msgContentForMsgList_SingleRound.length > 1) {
                        //清空-保持不可用 撤销&重问-恢复可用
                        this.btnDisabledState_Clear = true;
                        this.btnDisabledState_Undo = false;
                    } else {
                        this.btnDisabledState_Clear = true;
                        this.btnDisabledState_Undo = true;
                    }

                    this.sentext = this.settingsLanguage == 'cn' ? '发送' : 'Send';
                    this.apibtntext = this.settingsLanguage == 'cn' ? '已验证' : 'Checked';
                }
            },
            //API Key输入框 内容发生改变时
            inputapichange(showtips) {
                let ischanged = true; //状态，true为已变动，false为无变化
                let notif = '';

                //同时对比两项，因为代码里this.apiURL不可能为空，所以无需考虑this.apiCheckedData初始数据为空的情况，即不存在“空对空”返回true的情况。
                //v7.10之前是... key+url绑定  const isExist = this.apiCheckedData.some(item => item.apikey.trim() == this.api.trim() && item.apiurl.trim() == this.apiURL.trim());
                const isExist = this.apiCheckedData.some(item => item.apikey.trim() == this.api.trim());

                if (isExist) {

                    //按钮状态 启用 验证通过 key可用 可发送
                    this.changeButtonDisabledState(false);
                    if (showtips === true) {
                        notif = this.settingsLanguage == 'cn' ? 'API Key 可用' : 'API Key is valid';
                        setTimeout(() => {
                            this.showNotification(notif, 'bottom', '', 1700);
                        }, 2300);
                    }

                } else {

                    //按钮状态 禁用 验证失败 Key不可用
                    this.changeButtonDisabledState(true);

                    //v7.10之前... if ( showtips === true && this.apiCheckedData[0]['apikey'] !== '' && this.api.trim() !== '' ){ //有过验证成功。未曾成功过就不提醒了。
                    if (showtips === true && this.apiCheckedData.length > 0 && this.api.trim() !== '') { //有过验证成功=提醒。从未验证成功=不提醒。
                        notif = this.settingsLanguage == 'cn' ? '需要验证 API Key' : 'API Key needs to be checked';
                        setTimeout(() => {
                            this.showNotification(notif, 'bottom', '', 1800);
                        }, 2300);//前面一定有个通知，需要点间隔
                    }
                }
            },

            //一个开关:: 开始 发送按钮的动态文本状态
            beginLoadingAnimation() {
                this.endLoadingAnimation(); //先清除可能存在的计时器。验证按钮可重复点击，可能重复

                //  其他样式  模拟进度条效果 发送中的动态文字效果
                //	let texts = ["发送中▪▫▫", "发送中▫▪▫", "发送中▫▫▪"] ;
                //	let texts = ["发送中◈◇◇", "发送中◇◈◇", "发送中◇◇◈"] ;
                //	let texts = ["发送中▶▷▷", "发送中▷▶▷", "发送中▷▷▶"] ;
                //	let texts = ["发送中 ▏", "发送中 ▎", "发送中 ▍", "发送中 ▋", "发送中 ▊", "发送中 ▉"] ;  //还有一个位列第4的"▌"，PC端有些浏览器不兼容，与其他大小不一致，已去除。

                // 发送中的动态文字效果 Sending
                let texts;
                let texts_cn = ["发送中 ▁", "发送中 ▂", "发送中 ▃", "发送中 ▅", "发送中 ▆", "发送中 ▇"]; // 还有一个位列第4的"▄"，PC端有些浏览器不兼容，与其他大小不一致，去除了。
                let texts_en = ["Sending ▁", "Sending ▂", "Sending ▃", "Sending ▅", "Sending ▆", "Sending ▇"];
                texts = this.settingsLanguage == 'cn' ? texts_cn : texts_en;

                // 当btnDisabledState_CheckAPI为false时，验证按钮可用，表示未验证过api或api有变动需要重检
                // 发送消息时，btnDisabledState_CheckAPI一定为true，验证按钮一定是不可用。所以可用于判断是“发送”还是“验证key”
                if (!this.btnDisabledState_CheckAPI) {
                    // 验证中的动态文字效果  Checking  API Key
                    texts_cn = ["验证中◈◇◇", "验证中◇◈◇", "验证中◇◇◈"];
                    texts_en = ["Checking◈◇◇", "Checking◇◈◇", "Checking◇◇◈"];
                    texts = this.settingsLanguage == 'cn' ? texts_cn : texts_en;
                }
                let i = 0;
                //以下这行可改速度 末尾的数字代表间隔的时间 单位为毫秒。  如：默认580 表示0.58秒的间隔 比较慢
                this.timerId = setInterval(() => {
                    this.sentext = texts[i % texts.length];
                    i++;
                }, 580);
            },
            //一个开关:: 停止 发送按钮的动态文本状态
            endLoadingAnimation() {
                clearInterval(this.timerId);
            },

            //添加一键复制按钮（代码块） 插入按钮&按钮样式  addCopyButton (<pre><“add Copy Button”><code></code></pre>)  Copying code to the clipboard
            addCopyButtonToPreTags(msgDiv) {
                // 找到所有的pre code结构的元素
                const preTags = msgDiv.querySelectorAll('pre code');
                // 遍历每一个pre code元素
                preTags.forEach(preTag => {
                    // 判断其父元素是否为pre标签
                    if (preTag.parentElement.tagName.toLowerCase() === 'pre') {
                        // 在每一个code标签[前面]插入一个按钮 *必须是[前]
                        const copyBtn = document.createElement('button');
                        copyBtn.classList.add('copybtn');
                        copyBtn.setAttribute("data-clipboard-nextelementsibling", "");

                        //创建SVG图标 'copy svg'  svg by github
                        const copySVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
                        copySVG.setAttribute('height', '16');
                        copySVG.setAttribute('width', '16');
                        copySVG.setAttribute('viewBox', '0 0 16 16');
                        copySVG.setAttribute('version', '1.1');
                        copySVG.classList.add('clippy');
                        const path1 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
                        path1.setAttribute('d', 'M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z');
                        const path2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
                        path2.setAttribute('d', 'M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z');
                        copySVG.appendChild(path1);
                        copySVG.appendChild(path2);

                        const emptyDiv = document.createElement('div');
                        emptyDiv.appendChild(copySVG);
                        copyBtn.appendChild(emptyDiv);
                        preTag.parentElement.classList.add('snippet'); // <pre class="snippet">
                        preTag.parentElement.insertBefore(copyBtn, preTag);
                        //事件监听 EventListener
                        addEventListenerCopyBtnTooltip(copyBtn);
                    }
                });
            },

            //添加一键复制按钮（聊天气泡框） 插入按钮&按钮样式  addCopyButton   Copying Chat Message to the clipboard
            addCopyButtonToChatMessage(msgDivParentElement) {

                const copyBtn = document.createElement('button');
                copyBtn.classList.add('copybtn');
                copyBtn.setAttribute("data-clipboard-nextelementsibling", "");

                //创建SVG图标 'copy svg'   svg designed by clipboardjs https://clipboardjs.com/assets/images/clippy.svg
                const copySVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
                copySVG.setAttribute('width', '14');
                copySVG.setAttribute('height', '17');
                copySVG.setAttribute('fill', '#191919');
                copySVG.classList.add('clippy');
                const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
                path.setAttribute('d', 'm2.36373,12.61912l4.12233,0l0,1.03058l-4.12233,0l0,-1.03058zm5.15291,-6.18349l-5.15291,0l0,1.03058l5.15291,0l0,-1.03058zm2.06116,3.09174l0,-2.06116l-3.09174,3.09174l3.09174,3.09174l0,-2.06116l5.15291,0l0,-2.06116l-5.15291,0zm-4.63761,-1.03058l-2.57645,0l0,1.03058l2.57645,0l0,-1.03058zm-2.57645,3.09174l2.57645,0l0,-1.03058l-2.57645,0l0,1.03058zm9.27523,1.03058l1.03058,0l0,2.06116c-0.0161,0.28985 -0.11272,0.53139 -0.30595,0.72463s-0.43478,0.28985 -0.72463,0.30595l-10.30581,0c-0.5636,0 -1.03058,-0.46698 -1.03058,-1.03058l0,-11.33639c0,-0.5636 0.46698,-1.03058 1.03058,-1.03058l3.09175,0c0,-1.1433 0.91786,-2.06116 2.06116,-2.06116s2.06116,0.91786 2.06116,2.06116l3.09174,0c0.5636,0 1.03058,0.46698 1.03058,1.03058l0,5.15291l-1.03058,0l0,-3.09174l-10.30581,0l0,9.27523l10.30581,0l0,-2.06116zm-9.27523,-8.24465l8.24465,0c0,-0.5636 -0.46698,-1.03058 -1.03058,-1.03058l-1.03058,0c-0.5636,0 -1.03058,-0.46698 -1.03058,-1.03058s-0.46698,-1.03058 -1.03058,-1.03058s-1.03058,0.46698 -1.03058,1.03058s-0.46698,1.03058 -1.03058,1.03058l-1.03058,0c-0.5636,0 -1.03058,0.46698 -1.03058,1.03058z');
                copySVG.appendChild(path);

                const emptyDiv = document.createElement('div');
                emptyDiv.appendChild(copySVG);
                copyBtn.appendChild(emptyDiv);
                msgDivParentElement.prepend(copyBtn);
                //事件监听 EventListener
                addEventListenerCopyBtnTooltip(copyBtn);
            },

            // 初始化 批量添加一键复制按钮 (气泡框与代码块)
            initAddCopyButtonForMessage() {
                //.....firstDiv.innerHTML = firstMsgMarkdown; 6.19:改为v-html后，不再需要innerHTML

                const aimsgdivs = document.querySelectorAll('div.aimsg');
                const usermsgdivs = document.querySelectorAll('div.usermsg');
                for (let i = 0; i < aimsgdivs.length; i++) {
                    //搜索div内容，找到符合条件的代码块，添加一键复制按钮。 addCopyButton.  Copying code to the clipboard
                    this.addCopyButtonToPreTags(aimsgdivs[i]);
                    //一键复制按钮。为聊天气泡框添加一键复制按钮。 addCopyButton.  Copying Chat Message to the clipboard
                    this.addCopyButtonToChatMessage(aimsgdivs[i].parentElement);
                }
                for (let i = 0; i < usermsgdivs.length; i++) {
                    //搜索div内容，找到符合条件的代码块，添加一键复制按钮。 addCopyButton.  Copying code to the clipboard
                    this.addCopyButtonToPreTags(usermsgdivs[i]);
                    //一键复制按钮。为聊天气泡框添加一键复制按钮。 addCopyButton.  Copying Chat Message to the clipboard
                    this.addCopyButtonToChatMessage(usermsgdivs[i].parentElement);
                }
            },

            //marked.js初始化设置  Init Markdown
            initMarkdown() {
                // marked.js 与 highlight.js 判断是否加载成功   whether marked.js and highlight.js have been loaded or not.
                if (window.marked != null & window.hljs != null) {
                    // 加载完毕 开始初始化marked.js After loading the JS plugin, start initializing the settings for marked.js
                    marked.setOptions({
                        renderer: new marked.Renderer(),
                        sanitize: true, // 必须要有，否则有些内容不能发，会出错或不可见。* 有些js代码被编译为HTML时，可能会执行script里的代码
                        smartLists: true,
                        silent: true,

                        highlight: function (code) {
                            return hljs.highlightAuto(code).value; // hljs = highlight.js 代码高亮
                        }
                    });

                    //网页初始化: 变量、数据、事件等...
                    this.initialization();

                }
            },

            // 网页初始化: 变量、数据、事件等...
            initialization() {

                // 1). 设置默认值 Setting the default values.
                // 读取"本地存储"前的一些默认设置。Below are some default settings that are set before reading local cache.

                //v7.22  用户设置-变量的默认设置  将变量设为代码默认值，默认值以userVariables_default为准
                this.setVariablesToThis(this.userVariables_default, this.userVariables_default, true);

                // 可修改 Editable. 默认的GPT头像-方案A（写在代码里的绿色SVG头像）二选一 Default GPT Avatar. Plan-A (GPT Green SVG). You can only choose either Plan A or Plan B.
                this.isgptGreenSvgShow = true; //显示<svg>+隐藏<img>。 display<svg> + hide<img>
                this.gptAvatarURL = '';  // 当前GPT头像的图片地址。GPT Avatar URL. <img> url ='' (<img> is hide)
                this.inputGPTImageUrl = this.settingsLanguage == 'cn' ? this.GPTGreenSVG_UrlText_cn : this.GPTGreenSVG_UrlText_en; // 显示在输入框的GPT头像的地址 input text

                // 可修改 Editable. 默认的GPT头像-方案B（任意一张图片地址） 二选一  Default GPT Avatar. Plan-B (Any image URL.).  You can only choose either Plan A or Plan B.
                //		this.isgptGreenSvgShow = false; //隐藏<svg>+显示<img>。 display<img> + hide<svg>
                //		this.gptAvatarURL =  '/img/favicon.ico';  // 当前GPT头像的图片地址。GPT Avatar URL. <img> url = Any image URL
                //		this.inputGPTImageUrl = this.gptAvatarURL; // 显示在输入框的GPT头像的地址 input text


                let isNewChatData = false;
                let isLocalStorageRestored; //是否成功恢复数据

                //允许缓存时 && 浏览器当前支持localStorage时
                if (this.isLocalStorageAllowed && this.isLocalStorageSupported) {

                    // 2). 恢复本地存储数据 Restoring the local storage data.
                    //读取本地存储localStorage
                    //1.恢复用户设置：头像、个人设置... Read the local storage (localStorage), restore user settings: avatar, personal settings...
                    //2.恢复聊天记录  Read the local storage (localStorage), restore chat data
                    //3.恢复提示词模板  Read the local storage (localStorage), restore prompt word templates
                    //4.恢复API Key  Read the local storage (localStorage), restore API Key
                    isLocalStorageRestored = this.localStorage_RestoreData();


                    //本地有数据时 && 对话数据不为0
                    if (isLocalStorageRestored === true && this.chatDataList.length > 0) {
                        // 3). 恢复数据后，需要改变样式或数据的地方。 Once the data is restored, need to change the Style / Data.
                        // -- Start
                        this.changePageScaling(false);//需要在changeSimpleMode之前
                        this.changeLanguage();
                        this.changeSimpleMode(false);

                        this.$nextTick(() => {
                            // 1 2 3 4 5 6
                            this.todoList_ChatDataRestored('hasdata', 800);
                        });

                        //打开对话列表
                        this.isShowDialog_ChatList = false;
                        // -- End.
                    } else {
                        //其他情况
                        isNewChatData = true;
                    }

                } else {
                    //不允许本地存储 或 浏览器当前不支持localStorage
                    isNewChatData = true;
                }

                if (isNewChatData) { // 空网页 添加新对话
                    this.chatList_addNewChatData(true);

                    this.changePageScaling(false);//需要在changeSimpleMode之前
                    this.changeLanguage();
                    this.changeSimpleMode(false);
                    // 1 3 4 5 7 this.todoList_ChatDataRestored 写在 this.chatList_addNewChatData 中

                    //提醒语言支持英语 Remind users that the webpage supports English.
                    let lan1 = '', lan2 = '', lan3 = '';
                    if (navigator.language) {
                        lan1 = navigator.language.substr(0, 2);
                    }
                    if (navigator.userLanguage) {
                        lan2 = navigator.userLanguage.substr(0, 2);
                    }
                    if (navigator.languages && navigator.languages.length > 0) {
                        lan3 = navigator.languages[0].substr(0, 2);
                    }
                    if (this.settingsLanguage == 'cn' && lan1 != 'zh' && lan2 != 'zh' && lan3 != 'zh') {
                        //没有任何中文标记，非中文环境，提示可进行语言设置
                        const notif = 'Change the language. Click on the gear icon > Language, and select English.';
                        this.showNotification(notif, 'bottom', '', 4000);
                    }
                }

                //加载默认API Key数据 Initialize Default ApiKeysArray
                try {
                    //加载代码中的API Key，追加Key，如果已经存在(前面代码已经加载了本地数据)，则会忽略
                    this.submitBulkApiKeys(false, false, this.default_apiKeyArray_WrittenInTheCode)
                } catch (error) {
                    console.error('submitBulkApiKeys - 加载默认API Key数据 Initialize Default ApiKeysArray，出错了', error);
                }

                //恢复API Key，不直接恢复，分情况
                this.$nextTick(() => {
                    setTimeout(() => { //必须延迟，延迟0
                        if (!this.btnDisabledState_CheckAPI && this.apiCheckedData.length > 0) { //未通过验证的状态 && 存在已验证过的apikey
                            if (this.api.trim() === '') {
                                //恢复apikey到输入框，并恢复按钮状态
                                this.restoreApiKeyInput(false, false);
                            } else {
                                const notif = this.settingsLanguage == 'cn' ? '是否需要恢复一个可用的密钥 (API Key) ?' : 'Do you want to restore an available API Key ?';
                                setTimeout(() => {
                                    if (!this.btnDisabledState_CheckAPI) { //再次判断，保险，防止意外情况
                                        this.is_Alert_Confirm_Prompt_Open = true;
                                        if (confirm(notif)) {//弹窗询问是否恢复
                                            //恢复apikey到输入框，并恢复按钮状态
                                            this.restoreApiKeyInput(false, true);
                                        }
                                        setTimeout(() => {
                                            this.is_Alert_Confirm_Prompt_Open = false;
                                        }, 100);
                                    }
                                }, 1500);
                            }
                        }
                    }, 0);
                });

                //检测当前模型是否过期  弹通知提醒
                this.gptModel_CheckExpiration(this.apiGPTModel, 16000);//延迟16秒

                //1.mac电脑+2.使用safari浏览器+3.直接打开html文件（非本地web服务），三条件同时存在时，storage事件无法激活。解决方式：改变任一条件
                //监听事件 在所有标签页中添加 StorageEvent 监听器
                window.addEventListener('storage', () => {
                    // event.key 表示变化的 LocalStorage 键名
                    // event.newValue 表示变化后的 LocalStorage 值
                    // event.oldValue 表示变化前的 LocalStorage 值 // event.oldValue: 修改前的值（仅在同源且该键存在的情况下才有效）
                    // event.storageArea 表示发生变化的 LocalStorage 对象 // event.storageArea: 发生变化的 localStorage 对象（即 window.localStorage）
                    // 通过比较变化前后的值，判断数据是否发生变化，如果发生变化，则进行数据同步操作
                    if (event.key === 'chatDataList') {
                        this.localStorageEvent_chatDataList(event.key, event.newValue, event.oldValue);
                    }
                    //其他：不同步[设置]。以最后一次自动保存的为准（最后一次关闭的页面，关闭前会自动保存，[设置]会被覆盖）
                    //其他：apipkey、提示词，也不同步，自动保存时会合并数据。
                });
                try {
                    const isMac_1 = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) && navigator.platform.toLowerCase().indexOf('mac') > -1 && navigator.userAgent.toLowerCase().indexOf('macintosh') > -1;
                    const isMobile_1 = navigator.maxTouchPoints ? false : navigator.maxTouchPoints > 2;
                    const isMobile_2 = RegExp(/Android|webOS|iPhone|iPod|iPad/i).test(navigator.userAgent);
                    const isWin = typeof window !== 'undefined' && /Win/i.test(navigator.platform);
                    const isEdge = /Edg/i.test(navigator.userAgent);
                    const isChrome = /Chrome/.test(navigator.userAgent);
                    const isSafariOnMac = isMac_1 && !isMobile_1 && !isMobile_2 && !isWin && !isEdge && !isChrome;
                    const isLocal = window.location.protocol === 'file:';
                    // Mac+Safari+'file:' &&允许Allowed &&支持Supported
                    if (isSafariOnMac && isLocal && this.isLocalStorageAllowed && this.isLocalStorageSupported) {
                        if (!isLocalStorageRestored) { //首次弹出，只弹一次。 First time, Only pop up once。
                            let delaytime = this.isShowNotification ? 4000 : 2000;
                            const notif_cn = '在Mac电脑上，如果您使用Safari直接打开本地的.html文件，即Mac电脑、Safari浏览器以及网页地址以"file:"开头，这三个条件同时存在时，标签页之间的数据同步功能将失效。\r\n要解决这个问题，只需改变其中任何一个条件即可。您可以尝试换个浏览器，或者使用在线的版本，或者使用Mac自带的本地web服务器功能。';
                            const notif_en = 'If you open a local .html file directly in Safari on a Mac computer, under the conditions of using Safari browser, Mac computer, and having the URL starting with "file:", the data syncing feature between tabs will be disabled. To resolve this issue, you can try changing any one of these conditions. You can switch to a different browser, use an online version, or utilize the built-in local web server functionality on Mac.';
                            const notif = this.settingsLanguage == 'cn' ? notif_cn + '\r\n\r\n' + notif_en : notif_en;
                            const title = this.settingsLanguage == 'cn' ? '提醒 Tip' : 'Tip';
                            setTimeout(() => {
                                this.showNotification(notif, 'center', title);
                            }, delaytime);
                        }
                    }
                } catch (e) {
                }

                //监听事件 快捷键
                document.addEventListener('keydown', this.handleShortcut);

                //事件： 设置窗口 - 下拉框移除焦点
                document.getElementById("dialog-settings").addEventListener("change", function (event) {
                    if (event.target.classList.contains("dropdown")) {
                        event.target.blur(); // 将焦点移除
                    }
                });

                //保存到localStorage的5个事件。重复触发 ，只会保存一次。Repetitive triggering of the event will only be saved once.
                //监听事件1 visibilitychange
                document.addEventListener('visibilitychange', () => {
                    if (document.visibilityState === 'hidden') {
                        this.localStorage_SaveData(event);
                    }
                });
                //监听事件2 pagehide
                window.addEventListener('pagehide', () => {
                    this.localStorage_SaveData(event);
                });
                //监听事件3 onblur
                window.onblur = () => {// 通过window失去焦点事件来判断用户是否离开当前标签页（不可见）
                    if (!this.is_Alert_Confirm_Prompt_Open) {
                        let event = {type: "WindowBlur"};
                        this.localStorage_SaveData(event);
                    }
                };
                //监听事件4 unload
                window.addEventListener('unload', () => {
                    this.localStorage_SaveData(event);
                });
                let isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; // 判断是否为 iOS 设备
                //监听事件5 非ios设备 beforeunload
                if (!isIOS) {
                    window.addEventListener('beforeunload', () => {
                        this.localStorage_SaveData(event);
                    });
                }

            },

            // 初始化the first message (first message ->> markdown)
            initTheFirstMessage() {
                // 与上下文无关，打开网页时/新增对话时 对“欢迎语”（first massage）进行渲染，支持markdown。 渲染之前，界面上不显示。
                let firstMsgMarkdown = marked.parse(this.thefirstmessage);
                if (this.thefirstmessage.trim() == "" || this.thefirstmessage.replace(/^\n|\n$/g, "").trim() == "") {
                    firstMsgMarkdown = "<p> </p>";
                } else if (firstMsgMarkdown.trim() == "") {
                    firstMsgMarkdown = "<p>" + this.thefirstmessage + "</p>"; //v5.16 BUG A ： 问题不为空，但marked处理后变成空，则userMsgMarkdown重置为未经过marked处理的原文
                }
                if (firstMsgMarkdown.trim() == "") {
                    firstMsgMarkdown = "<p> </p>"; // v5.16 BUG B ： 若为空，则输出加了<p>标签的空格，撑起高度，保持正确的气泡框样式
                }
                if (this.msgList.length > 0) {
                    this.msgList[0]["msg"] = this.msgList[0]['msg'] == "" ? firstMsgMarkdown : this.msgList[0]['msg'];  //通过v-html绑定，无需写在this.$nextTick(()。  默认是空，不等于空说明读取了缓存。  *排除故意设为空的情况
                }
            },

            //clipboard.js初始化设置  Init Clipboard (one-click copy button)
            initClipboard() {
                // 定义一键复制按钮的功能：指定复制的对象/内容 & 完成后的事件. Setting up the function of "one-click copy button": 1. Setting the object and content that need to be copied. 2. Defining the event after the copy is completed.
                //if ( typeof ClipboardJS != 'undefined')

                // A: Copy the text content of the next sibling element.  初始化clipboard一键复制{代码块}功能  复制对象：同一层次结构中的下一个元素中的文本信息，所以<button>要放在复制对象的前面
                var clipboardNextElementSibling = new ClipboardJS('[data-clipboard-nextelementsibling]', {
                    target: function (trigger) {
                        return trigger.nextElementSibling;
                    }
                });

                // A: 复制成功
                clipboardNextElementSibling.on('success', function (e) {
                    //console.info('Action:', e.action);
                    //console.info('Text:', e.text);
                    //console.info('Trigger:', e.trigger);
                    e.clearSelection();
                    document.activeElement.blur(); //让按钮失去焦点。 这句代码包含在clipboard.min.js 2.0.11之前的版本中，但2.0.11版本取消了。The code exists in versions of clipboard.min.js lower than 2.0.11, but was removed in version 2.0.11.

                    // Tooltip Direction
                    // 1. 复制对象：代码块内容 e.trigger.parentElement.classList.contains('snippet') 提示框方向: 左 / 西    <pre class='snippet'> -->> <code>  Tooltip Direction:tooltipped-w
                    let directionClass = 'tooltipped-w';
                    // 2. 复制对象：右侧用户  e.trigger.nextElementSibling.classList.contains('usermsg') 提示框方向: 左上 / 西北   User. Tooltip Direction:tooltipped-nw
                    if (e.trigger.nextElementSibling.classList.contains('usermsg')) {
                        directionClass = 'tooltipped-nw';
                    }
                    // 3. 复制对象：左侧AI回复  e.trigger.nextElementSibling.classList.contains('aimsg') 提示框方向: 右上 / 东北   AI.  Tooltip Direction:tooltipped-ne
                    if (e.trigger.nextElementSibling.classList.contains('aimsg')) {
                        directionClass = 'tooltipped-ne';
                    }
                    // 4. 复制对象：设置弹窗 - 复制API Key  e.trigger.nextElementSibling.classList.contains('inputghostforcopyapikey') 提示框方向: 上 / 北    Tooltip Direction:tooltipped-n
                    if (e.trigger.nextElementSibling.classList.contains('inputghostforcopyapikey')) {
                        directionClass = 'tooltipped-n';
                    }

                    const msg = window.currentlang == 'cn' ? '已复制' : 'Copied!';
                    showCopyBtnTooltip(e.trigger, msg, directionClass);
                });

                // A: 复制失败
                clipboardNextElementSibling.on('error', function (e) {
                    //console.error('Action:', e.action);
                    //console.error('Trigger:', e.trigger);

                    // Tooltip Direction
                    // 默认代码块  左 / 西  <pre class='snippet'> -->> <code>
                    let directionClass = 'tooltipped-w';
                    // usermsg & aimsg  Tooltip可能会超出屏幕，失败几率小，只做简单处理：换个方向提醒
                    if (e.trigger.nextElementSibling.classList.contains('usermsg')) {
                        directionClass = 'tooltipped-ne';
                    } else if (e.trigger.nextElementSibling.classList.contains('aimsg')) {
                        directionClass = 'tooltipped-nw';
                    } else if (e.trigger.nextElementSibling.classList.contains('inputghostforcopyapikey')) {
                        // 4. 复制对象：设置弹窗 - 复制API Key  提示框方向变为: 右上 / 东北
                        directionClass = 'tooltipped-ne';
                    }

                    showCopyBtnTooltip(e.trigger, fallbackCopyBtnMessage(e.action), directionClass);
                });

                // B: ...

            },


            // 异步加载js插件，CDN首选链接*1 +备用链接*2 or more.   Async JS loading. url is the main CDN link, fallbackUrl is the backup CDN link (link*2), and callback is the operation after the loading is complete.
            loadScriptFromCDN(url, fallbackUrls, callback) {
                let script = document.createElement("script");
                // 加载参数url - CDN地址
                script.src = url;
                document.head.appendChild(script);
                // 监听:url地址失效了
                script.onerror = () => {
                    // 按顺序循环加载备用链接fallbackUrls(数组)，一旦成功就callback
                    if (fallbackUrls && fallbackUrls.length > 0) {
                        let fallbackUrl = fallbackUrls.shift();
                        this.loadScriptFromCDN(fallbackUrl, fallbackUrls, callback);
                    } else {
                        console.error("所有CDN链接均失效。最后尝试的备用链接是(CDN Failed)：" + url);
                        if (!this.isShowNotification) {
                            if (!this.setNotificationContent.includes('Loading Error')) {
                                //首次打开 / 首个错误
                                this.setNotificationContent = this.settingsLanguage == 'cn' ? '网页加载错误列表 ( Loading Error List)：' : 'Loading Error List:';
                            }
                            //手动设置，不使用this.showNotification 原因：手动设置代码少 因为要考虑是否首次打（过程较长，可能会主动关闭，也可能误关） 及this.showNotification有延迟会导致这边也要设置延迟。
                            this.setNotificationPosition = 'center';
                            this.isNotificationHasMask = true;
                            this.notificationTimeoutId = -1;
                            this.setNotificationTitle = this.settingsLanguage == 'cn' ? '错误 Error' : 'Error'; // 多余的
                            this.isShowNotification = true;
                        }

                        //语言为中文时，为什么要显示双语？ 因为首次使用时可能就会报错，但代码默认语言为中文，所以首次出错应该显示双语，避免出错时访问的用户看不懂中文。
                        let err_cn = '';
                        let err_en = '';
                        //错误提醒
                        if (url.includes('marked')) {
                            err_cn = "\r\n\r\n☒ !! 错误：Markdown插件marked.js加载失败，将无法使用GPT！请检查网络后刷新页面。  !! Error:Failed to load plugin marked.js. The GPT will be unavailable. Please check your network and refresh the page to try again.";
                            err_en = "\r\n\r\n☒ !! Error:Failed to load plugin marked.js. The GPT will be unavailable. Please check your network and refresh the page to try again.";
                            this.setNotificationContent += this.settingsLanguage == 'cn' ? err_cn : err_en; //追加错误信息
                            this.msgList[0]['msg'] = '<p>' + this.thefirstmessage + '</p>'; // 如果marked加载失败，就把未经格式化的第一条信息显示出来，显示原始文本
                        } else if (url.includes('highlight')) {
                            err_cn = "\r\n\r\n☒ !! 错误：代码高亮插件highlight.js加载失败，将无法使用GPT！请检查网络后刷新页面。  !! Error: Failed to load plugin highlight.js. The GPT will be unavailable. Please check your network and refresh the page to try again.";
                            err_en = "\r\n\r\n☒ !! Error: Failed to load plugin highlight.js.The GPT will be unavailable. Please check your network and refresh the page to try again.";
                            this.setNotificationContent += this.settingsLanguage == 'cn' ? err_cn : err_en; //追加错误信息
                        } else if (url.includes('axios')) {
                            err_cn = "\r\n\r\n☒ !! 错误：HTTP库Axios.js加载失败，将无法使用GPT！请检查网络后刷新页面。  !! Error: The HTTP library (Axios.js) failed to load. The GPT will be unavailable. Please check your network and refresh the page to try again.";
                            err_en = "\r\n\r\n☒ !! Error: The HTTP library (Axios.js) failed to load. The GPT will be unavailable. Please check your network and refresh the page to try again.";
                            this.setNotificationContent += this.settingsLanguage == 'cn' ? err_cn : err_en; //追加错误信息
                        } else if (url.includes('clipboard')) {
                            err_cn = "\r\n\r\n☒ 错误：一键复制插件clipboard.js加载失败，复制按钮功能将失效，不影响其他功能，可继续使用。（建议先刷新页面重试一次）  Error: (Please refresh the page once.) The clipboard plugin clipboard.js failed to load, but GPT can still be used. The copy button will not work, but other features can still be used normally.";
                            err_en = "\r\n\r\n☒ Error: (Please refresh the page once.) The clipboard plugin clipboard.js failed to load, but GPT can still be used. The copy button will not work, but other features can still be used normally.";
                            this.setNotificationContent += this.settingsLanguage == 'cn' ? err_cn : err_en; //追加错误信息
                        }
                    }
                };
                // 监听:url加载成功
                script.onload = callback;
            },

            // 异步加载css样式，CDN首选链接*1 +备用链接*2 or more.  Async CSS loading.
            loadCssFileFromCDN(url, backupUrls) {
                let link = document.createElement("link");
                link.rel = "stylesheet";
                link.type = "text/css";
                // 加载参数url - CDN地址
                link.href = url;
                document.head.appendChild(link);
                // 监听:url地址失效了
                link.onerror = () => {
                    if (backupUrls && backupUrls.length > 0) {
                        let nextUrl = backupUrls.shift();
                        this.loadCssFileFromCDN(nextUrl, backupUrls);
                    } else {
                        console.error("所有CSS的CDN链接均失效(CDN Failed)，仅缺失部分样式，不影响功能的正常使用。最后尝试的备用链接是：" + url);
                    }
                };
            },

        },

        mounted() {

            // 创建连接
            this.initWebSocket();

            this.defaultAPIKey = this.api.trim(); //记录代码中的api（如果存在的话），因为之后this.api会被覆盖
            try {
                //过滤空值，过滤空格
                this.default_apiKeyArray_WrittenInTheCode = this.default_apiKeyArray_WrittenInTheCode.filter(apiKey => apiKey.trim() !== '').map(apiKey => apiKey.trim());
            } catch (error) {
                this.default_apiKeyArray_WrittenInTheCode = []; //出错则设置为空
                console.error('default_apiKeyArray_WrittenInTheCode - 写在代码中的api key存在问题，已清空处理，不影响使用。', error);
            }

            //删除apikey为空的项，定义变量时 写了空的Key 需要在这里删除（代码需要靠前）
            this.apiCheckedData = this.apiCheckedData.filter(apidata => apidata.apikey && apidata.apikey.trim() !== '');//排除空项

            this.current_Year = new Date().getFullYear().toString();//更新当前的年份变量,String类型

            //v7.22 不存储数据 delete this.apikeyData_default.api = JSON.parse(JSON.stringify(this.api.trim()));
            //v7.22 不存储数据 delete this.apikeyData_default.apiCheckedData = JSON.parse(JSON.stringify(this.apiCheckedData));
            this.promptsData_default.gptSystemPrompt = JSON.parse(JSON.stringify(this.gptSystemPrompt));//待定 待处理
            //v7.22 不存储数据 delete this.promptsData_default.gptPrompts = JSON.parse(JSON.stringify(this.gptPrompts));

            //先渲染一个带有<p>标签的空格，有内容，有行高。“保留一个空格，用来增加高度。在渲染首条消息前，有内容撑开高度，可以盖住气泡框左边的箭头。 Keep an empty space to increase the height. Before rendering the first message, the normal height can cover the left arrow.”
            const first_msg_div = document.querySelectorAll('div.aimsg')[0];
            first_msg_div.innerHTML = '<p> </p>';

            // 添加'页面缩放下拉框'的选项 (移动设备页面缩放的比例)
            for (let i = 0.5; i < 1.01; i += 0.05) {
                this.selectedPageScalingOptions.push((i.toFixed(2) * 100).toFixed(0) + '%');
            }

            //隐藏遮罩。网页Loading加载阶段，遮罩可以避免加载慢导致闪屏/弹窗闪现/显示vue源代码等问题
            const loadingmask = document.getElementById("loading-mask");
            if (loadingmask != null) {
                loadingmask.classList.add('dialog-wrapper-hide');
            }

            //检测浏览器是否支持 本地存储localStorage
            this.isLocalStorageSupported = this.checkLocalStorageSupport();


            // 异步加载开源的js插件、css样式，减少打开网页卡顿的现象。首选Unpkg的链接(国外常用CDN，fan.qiang后更稳定)，备选BootCDN(国内常用CDN)+备选CDNJS(国外常用CDN)
            // For regions outside of China, BootCDN may have slower speeds and can be placed at the bottom of the list or replaced with another CDN.

            // 1.异步加载marked.min.js  必备，失效将无法聊天
            // marked.js 开源的Markdown插件 官方: https://github.com/markedjs/marked
            this.loadScriptFromCDN(
                "/blog/editormd/lib/marked.min.js",
                [
                    "https://cdn.bootcdn.net/ajax/libs/marked/4.3.0/marked.min.js",
                    "https://cdnjs.cloudflare.com/ajax/libs/marked/4.3.0/marked.min.js",
                ],
                () => {
                    //一旦加载成功，初始化
                    this.initMarkdown();
                }
            );

            // 2.异步加载highlight.min.js  失效也可正常聊天，影响一般
            // highlight.js  开源的代码高亮插件  官方: https://github.com/highlightjs/highlight.js/
            this.loadScriptFromCDN(
                "/ajax/libs/highlight/highlight.min.js",
                [
                    "https://cdn.bootcdn.net/ajax/libs/highlight.js/11.7.0/highlight.min.js",
                    "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js",
                ],
                () => {
                    //一旦加载成功，初始化
                    this.initMarkdown();
                }
            );

            // 3.异步加载axios.min.js  必备，失效将无法聊天，需要使用axios与GPT服务器进行连线。
            // axios.js  Axios是一种常用HTTP库，用于向服务器发起HTTP请求。   官方: https://github.com/axios/axios/
            this.loadScriptFromCDN(
                "/ajax/libs/axios/axios.min.js",
                [
                    "https://cdn.bootcdn.net/ajax/libs/axios/1.3.2/axios.min.js",
                    "https://cdnjs.cloudflare.com/ajax/libs/axios/1.3.2/axios.min.js",
                ],
                () => {
                    //成功即可，无需操作
                }
            );

            // 4.异步加载clipboard.min.js  失效也可正常聊天，影响非常小
            // clipboard.js  开源的一键复制按钮插件  官方: https://github.com/zenorocha/clipboard.js/  https://clipboardjs.com
            this.loadScriptFromCDN(
                "/ajax/libs/clipboard/clipboard.min.js",
                [
                    "https://cdn.bootcdn.net/ajax/libs/clipboard.js/2.0.11/clipboard.min.js",
                    "https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.11/clipboard.min.js",
                ],
                () => {
                    //一旦加载成功，初始化
                    this.initClipboard();
                }
            );

            // 5.异步加载panda-syntax-dark.min.css  失效也可正常聊天，影响非常小
            // Markdown代码高亮的css样式表，即配色方案。 方案名：Panda Syntax Dark  其他配色方案：https://highlightjs.org/static/demo/
            this.loadCssFileFromCDN(
                "/ajax/libs/highlight/panda-syntax-dark.min.css",
                [
                    "https://cdn.bootcdn.net/ajax/libs/highlight.js/11.7.0/styles/panda-syntax-dark.min.css",
                    "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/panda-syntax-dark.min.css"
                ]
            );

            //手动为单独的一键复制按钮添加事件
            //复制 API Key
            const copyapikeybtn = document.querySelector('.btncopyapikey'); // Copy API Key
            addEventListenerCopyBtnTooltip(copyapikeybtn);
            //复制 消息通知 弹窗内容
            const copynotification = document.querySelector('.btncopynotification'); // Copy Notification
            addEventListenerCopyBtnTooltip(copynotification);

            // 获取#dialog-promptgenerator提示词弹窗中所有class为copybtn的元素
            // 之后的版本：提示词窗口里一键复制按钮会很多，后续需要确认：1、复制的对象位置是否正确。2、复制的方式是否为同一种。
            let copyBtns = document.querySelectorAll('#dialog-promptgenerator .copybtn');
            // 为每个元素添加事件
            for (let i = 0; i < copyBtns.length; i++) {
                addEventListenerCopyBtnTooltip(copyBtns[i]);
            }

            //隐形提示词 如果在代码中写了gptSystemPrompt_hidden隐藏提示词，则禁用提示词弹窗按钮（6.23 改为绑定）。
            if (this.gptSystemPrompt_hidden.trim() != '') {
                this.gptSystemPromptReadOnly = "hidden system prompt";
            }

            //初始化 添加上传头像的控件
            this.addInput_FileDialog_Avatar('user');
            this.addInput_FileDialog_Avatar('gpt');

            //以上 非数据类初始化。 Above, unrelated to data.


            // 其他初始化: 变量初始化、读取本地缓存数据、事件等... 改到this.initialization()，按执行顺序，放在initMarkdown()中。

        },

        beforeDestroy() {
            //快捷键 事件 移除监听
            document.removeEventListener('keydown', this.handleShortcut);
        }
    }).mount('#app')
</script>

<script>
    //聊天输入框自动调整高度 Chat input box with auto-adjustable height    *关于输入框高度的上下限的设置，搜 max-height 和 maxHeight
    //v0515 代码加入<!DOCTYPE html>后 ，Height计算方式发生改变。Add <!DOCTYPE html>, the calculation method of Height has changed.

    let textarea_Chat = document.querySelector('.textareachatinputbox');

    textarea_Chat.addEventListener('input', (e) => {
        textarea_Chat.style.height = '38px';
        textarea_Chat.style.height = e.target.scrollHeight + 'px';

        //max-height当变量用，存储高度。另一处：聊天输入框获取焦点时，恢复此高度。 *以下代码必须置后，否则剪切输入框内容时会有bug
        if (parseInt(e.target.scrollHeight) > 38) {
            let height6px = parseInt(e.target.scrollHeight) + 6; // +
            if (parseInt(e.target.scrollHeight) < 450) {
                //textarea_Chat.style.maxHeight = e.target.scrollHeight + 'px'; // -
                textarea_Chat.style.maxHeight = height6px.toString() + 'px'; // +
            } else {
                textarea_Chat.style.maxHeight = '456px'; // + 6
            }
            textarea_Chat.style.height = height6px.toString() + 'px'; // +
            textarea_Chat.style.borderRadius = '6px 6px 6px 0px'; //border-radius: 6px 6px 6px 0px;
        } else {
            textarea_Chat.style.maxHeight = '38px';
            textarea_Chat.style.borderRadius = '0px 6px 6px 0px'; //border-radius: 0px 6px 6px 0px;
        }
    });
</script>


<script>
    //"移动端iphone&ipad"与"移动端/电脑端的非WebKit内核浏览器"均不支持"-webkit-scrollbar"自定义滚动条样式(body)。所以做些调整，让网页右侧不会出现太宽或太窄的情况。
    //The scrollbar style "-webkit-scrollbar"(body) is not supported on iPhones, iPads, and non-WebKit core browsers.
    //我不是很懂调整css样式，

    try {
        let isFirefox = navigator.userAgent.indexOf("Firefox") != -1; // 判断是否为 Firefox 内核
        let isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; // 判断是否为 iOS 设备
        let isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); // 判断是否为 Safari 内核
        let isChrome = /chrome/i.test(navigator.userAgent); // 判断是否为 Chrome 内核
        let isWebkit = /webkit/i.test(navigator.userAgent); // 判断是否为 WebKit 内核

        let style = document.createElement('style');
        style.type = 'text/css';
        if (isFirefox || isIOS) { // 如果是 Firefox 内核或 iOS 设备则添加样式
            style.innerHTML = '.userinfo { padding-right: 6px; } .btn { margin-right: 13px; }';
            document.getElementsByTagName('head')[0].appendChild(style);
        } else if (!isSafari && !isChrome && !isWebkit) {  // 如果不属于 Webkit 内核
            style.innerHTML = '.userinfo { padding-right: 3px; } .btn { margin-right: 10px; }';
            document.getElementsByTagName('head')[0].appendChild(style);
        }
    } catch {
    }
</script>

<script>
    //作为一款AI语言模型，我很荣幸能够为查询OpenAI API Key余额这个项目做出贡献。我具备出色的整合能力和阅读代码能力，能够快速理解和融合不同项目的代码和功能。同时，我也十分感谢其他开发者的付出和贡献，他们的代码和思路为我提供了很多启发和帮助。我要特别感谢以下开源项目的作者：@herobrine19的项目（https://github.com/herobrine19/openai-billing）和@ClarenceDan的项目（https://github.com/ClarenceDan/openai-billing）。这些项目为我提供了很多代码参考和技术支持，使我能够更好地完成查询OpenAI API Key余额这个任务。在开源社区中，我们应该互相尊重，分享和合作，共同推动开源技术的发展。
    //以下为[查询OpenAI API余额]的代码  Javascript code to check OpenAI API balances


    //声明变量，记录查询余额状态
    var isCheckAPIKey = false;
    //避免重复报错弹窗
    var isError = false;

    //连官网接口查询返回API Key对应的账号信息
    async function checkBilling(apiKey) {
        isError = false;
        // 计算起始日期和结束日期
        const now = new Date();
        let startDate = new Date(now - 90 * 24 * 60 * 60 * 1000); // 90天之前的日期  Default query for three months usage.
        const endDate = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 当前日期
        const subDate = new Date(now);
        subDate.setDate(1) // 本月1号的日期

        // 设置API请求URL和请求头
        // 官方接口1：可通过API Key查询账号信息（包含姓名、是否绑卡、额度、余额、额度有效期等，但不包含已消耗的金额）
        //ES6的写法： const urlSubscription = `https://api.openai.com/v1/dashboard/billing/subscription`; // 查是否订阅
        const urlSubscription = 'https://api.openai.com/v1/dashboard/billing/subscription'; // 查是否订阅  *兼容性更好 Support more browsers
        // 官方接口2：可通过API Key查询已消耗的金额
        //ES6的写法： let urlUsage = `https://api.openai.com/v1/dashboard/billing/usage?start_date=${formatDate(startDate)}&end_date=${formatDate(endDate)}`; // 查使用量
        let urlUsage = 'https://api.openai.com/v1/dashboard/billing/usage?start_date=' + formatDate(startDate) + '&end_date=' + formatDate(endDate); // 查使用量 *兼容性更好 Support more browsers
        const headers = {
            "Authorization": "Bearer " + apiKey,
            "Content-Type": "application/json"
        };

        //备注 其他接口：右边这个官方接口疑似从4月初开始失效  const urlBalance = `https://api.openai.com/dashboard/billing/credit_grants`; //查普通账单

        try {
            // 通过urlSubscription接口链接获取账号信息
            let response = await fetch(urlSubscription, {headers});
            if (!response.ok) {
                console.log("urlSubscription - response.ok:", response.ok);
                // Notification: Failed to check the balance. The reasons for the failure are not clear, and the possibility of "code malfunction" cannot be ruled out.(No impact on ChatGPT usage).
                const msg_cn = '!! 查询余额失败，存在多种可能，检查网络后重试(国内需要魔法)，仍失败的话，请放弃查余额～ \r\n\r\n不排除是查询余额代码失效的原因（不影响GPT的使用）。\r\n\r\n可查看代码是否有更新：\r\nhttps://gitee.com/GiteeKey/ruoyi-blog/ \r\nhttps://github.com/lin2025/gpt3.5/';
                const msg_en = "!! Failed to check the balance. The reasons for the failure are not clear, and the possibility of 'code malfunction' cannot be ruled out.(No impact on GPT usage) \r\n\r\nCheck for code updates:\r\nhttps://github.com/lin2025/gpt3.5/\r\n\r\nTip: If your location is not supported by OpenAI, use a VPN to check your balance.";
                const msg = window.currentlang == 'cn' ? msg_cn : msg_en;
                alert(msg);
                return;
            }
            const subscriptionData = await response.json();

            // 判断是否过期
            const timestamp_now = Math.floor(Date.now() / 1000); //当前时间的时间戳
            const timestamp_expire = subscriptionData.access_until; //到期时间的时间戳
            if (timestamp_now > timestamp_expire) {
                const msg_cn = 'OpenAI API的账号余额似乎已到期, 但不排除仍然能用，请登录OpenAI进行查看。\r\n查询结果正在生成...';
                const msg_en = "It seems that the OpenAI API account balance has expired, but it is still possible that it can still be used. Please log in to OpenAI to check. \r\nQuery results are being generated...";
                const msg = window.currentlang == 'cn' ? msg_cn : msg_en;
                alert(msg);
            }

            const totalAmount = subscriptionData.hard_limit_usd; //总额度 美元
            const is_subsrcibed = subscriptionData.has_payment_method; //是否绑卡用户

            // 通过urlUsage接口链接获取使用量  本次统计范围为：90天前～今天  Default query for three months usage.( has_payment = false)
            response = await fetch(urlUsage, {headers});
            let usageData = await response.json(); // 用户过去90天的使用数据
            let totalUsage = usageData.total_usage / 100; // 用户的累计用量（90天），即消耗的总金额。除以100后 单位为美元

            let usageDataCurr; //未绑卡用户的本月使用数据
            let totalUsageCurr; //未绑卡用户的本月用量

            startDate = subDate; // 开始时间改为本月第一天
            //ES6的写法 urlUsage = `https://api.openai.com/v1/dashboard/billing/usage?start_date=${formatDate(startDate)}&end_date=${formatDate(endDate)}`; // 新的时间 范围为本月
            urlUsage = 'https://api.openai.com/v1/dashboard/billing/usage?start_date=' + formatDate(startDate) + '&end_date=' + formatDate(endDate); // 新的时间 范围为本月 *兼容性更好 Support more browsers.

            //区别 1、如果用户绑了信用卡，额度每月会刷新，额度为本月额度。  2、如果用户未绑卡，则增加展示本月用量。
            if (is_subsrcibed) {
                // 如果用户绑卡，累计用量数据变更为本月用量数据   通过urlUsage接口链接重获使用量  本次统计范围为：本月1日～今天
                response = await fetch(urlUsage, {headers});
                usageData = await response.json(); //绑卡用户本月使用数据
                totalUsage = usageData.total_usage / 100; //绑卡用户本月用量
            } else {
                // 如果用户未绑卡，额外获取本月数据 ，比绑卡用户多展示一行数据  通过urlUsage接口链接获取本月使用量  本次统计范围为：本月1日～今天
                response = await fetch(urlUsage, {headers});
                usageDataCurr = await response.json(); //本月使用数据
                totalUsageCurr = usageDataCurr.total_usage / 100; //本月用量
            }

            // 计算剩余额度
            const remaining = totalAmount - totalUsage;

            // 数据说明：共9个参数，均为官方接口返回的信息，不必担心安全问题，无任何泄漏风险，不会以任何方式存储数据
            //  	来源官方接口1： https://api.openai.com/v1/dashboard/billing/subscription
            //  	来源官方接口2： https://api.openai.com/v1/dashboard/billing/usage?start_date=查询消耗金额的起始时间&end_date=查询消耗金额的结束时间
            // 数组序列如下：
            // 0 subscriptionData.account_name [账号归属/Account_Name]
            // 1 is_subsrcibed  即subscriptionData.has_payment_method [是否绑卡/has_payment_method]  ？绑卡和购买Plus应该是两回事，绑卡使用API 不一定有订阅Plus
            // 2 totalAmount  即subscriptionData.hard_limit_usd  [账号总额度/Total Amount]
            // 3 totalUsage  即usageData.total_usage(需要除以100)  [已消耗金额/Total Usage] //绑卡用户为本月用量  非绑卡用户为历史累计用量
            // 4 remaining (通过计算得出) [可用额度/Remaining]
            // 5 timestamp_expire  即subscriptionData.access_until  [截止时间/Expiration Date] *时间戳格式
            // 6 subscriptionData.plan["title"]  plan-title 可能是账号分类/用途
            // 7 subscriptionData.plan["id"]   Plan-id [订阅情况/Subscription]
            // 8 totalUsageCurr 未绑卡用户的本月用量
            // 以上 数据说明

            //返回通过官网接口获取到的9个数据
            return [subscriptionData.account_name, is_subsrcibed, totalAmount, totalUsage, remaining, timestamp_expire, subscriptionData.plan["title"], subscriptionData.plan["id"], totalUsageCurr];

        } catch (error) {
            isError = true;
            isCheckAPIKey = false;
            console.log(error);
            // Notification: Failed to check the balance. The reasons for the failure are not clear, and the possibility of "code malfunction" cannot be ruled out.(No impact on ChatGPT usage).
            const msg_cn = '!! 查询余额失败，存在多种可能，检查网络后重试(国内需要魔法)，仍失败的话，请放弃查余额～ \r\n\r\n不排除是查询余额代码失效的原因（不影响GPT的使用）。\r\n\r\n可查看代码是否有更新：\r\nhttps://gitee.com/GiteeKey/ruoyi-blog/ \r\nhttps://github.com/lin2025/gpt3.5/';
            const msg_en = "!! Failed to check the balance. The reasons for the failure are not clear, and the possibility of 'code malfunction' cannot be ruled out.(No impact on GPT usage) \r\n\r\nCheck for code updates:\r\nhttps://github.com/lin2025/gpt3.5/\r\n\r\nTip: If your location is not supported by OpenAI, use a VPN to check your balance.";
            const msg = window.currentlang == 'cn' ? msg_cn : msg_en;
            alert(msg);
            return [null, null, null, null, null, null, null, null, null]; //出错则返回9个空值
        }
    }


    //格式化日期
    function formatDate(date) {
        const year = date.getFullYear();
        let month, day;
        if (String.prototype.padStart) {
            month = (date.getMonth() + 1).toString().padStart(2, '0');
            day = date.getDate().toString().padStart(2, '0');
        } else {
            month = (date.getMonth() + 1).toString();
            day = date.getDate().toString();
            // 在月份和日期前补零
            if (month.length === 1) {
                month = '0' + month;
            }
            if (day.length === 1) {
                day = '0' + day;
            }
        }
        //ES6写法： return `${year}-${month}-${day}`;
        return year + '-' + month + '-' + day;  // 兼容性更好。 Support more browsers.
    }


    //查询API Key余额
    function sendRequest(apiKey_or_sessionKey) {

        let msg_cn, msg_en, msg;
        //如果后台正在查询，那么提醒后返回，防止重复查询
        if (isCheckAPIKey) {
            msg_cn = '已在为您查询OpenAI API的余额，请关闭弹窗后耐心等待。\r\n等待期间可进行其他操作，正常情况下数秒内完成，如等待时间过长，请检查下网络状态。';
            msg_en = "Your request is currently being processed. Please close the pop-up window and wait patiently.";
            msg = window.currentlang == 'cn' ? msg_cn : msg_en;
            alert(msg);
            return;
        }

        //1111111 余额代码失效，暂时注释掉  isCheckAPIKey = true;

        //1111111 余额代码失效，7.27 功能关闭
        //1111111 暂时注释掉 msg_cn = '将为您查询OpenAI API的账号余额，请先关闭本弹窗，待查询结果出来后会自动弹出。\r\n\r\n注意: 查余额需要魔法';
        //1111111 暂时注释掉 msg_en = "After closing this pop-up window, the account balance of the API will be checked. The query result will automatically pop up when it is available.\r\n\r\nTip: If your location is not supported by OpenAI, use a VPN to check your balance.";
        msg_cn = 'OpenAI API 账号余额查询功能已停用\r\n7月22日～26日，OpenAI 陆续停止了使用 API Key 来查询余额和其他账号信息的通道。现在需要使用 Session Key 来进行查询，但 Session Key 是动态变化的，目前暂时没有很好的余额查询方案，因此暂时停用此功能。';
        msg_en = 'The balance inquiry feature of the OpenAI API has been deactivated.\r\nFrom July 22nd to 26th, OpenAI suspended the ability to query balances and other account information using the API Key. Currently, to check the balance, a Session Key needs to be used. However, as the Session Key is dynamically changing, there is currently no viable solution to query the balance, which has resulted in the temporary suspension of this feature.';
        msg = window.currentlang == 'cn' ? msg_cn : msg_en;
        alert(msg);
        return; //add 1111111 7.27 功能关闭，add 返回

        //因为是在API Key验证成功后才能查询余额，所以此处无需判断API Key的有效性、格式是否正确等情况


        //连官网接口查询账号信息  传入网页上API Key后开始查询
        checkBilling(apiKey_or_sessionKey).then((data) => {

            //如果checkBilling已出错，就返回，避免重复报错。
            if (isError && data[0] == null && data[1] == null) {
                isCheckAPIKey = false;
                return;
            }
            // 数据说明：共9个参数，均为官方接口返回的信息，不必担心安全问题，无任何泄漏风险，不会以任何方式存储数据
            //  	来源官方接口1： https://api.openai.com/v1/dashboard/billing/subscription
            //  	来源官方接口2： https://api.openai.com/v1/dashboard/billing/usage?start_date=查询消耗金额的起始时间&end_date=查询消耗金额的结束时间
            // 数组序列如下：
            // 0 subscriptionData.account_name [账号归属/Account_Name]
            // 1 is_subsrcibed  即subscriptionData.has_payment_method [是否绑卡/has_payment_method]
            // 2 totalAmount  即subscriptionData.hard_limit_usd  [账号总额度/Total Amount]
            // 3 totalUsage  即usageData.total_usage(需要除以100)  [已消耗金额/Total Usage] //绑卡用户为本月用量  非绑卡用户为历史累计用量
            // 4 remaining (通过计算得出) [可用额度/Remaining]
            // 5 timestamp_expire  即subscriptionData.access_until  [截止时间/Expiration Date] *时间戳格式
            // 6 subscriptionData.plan["title"]  plan-title 可能是账号分类/用途
            // 7 subscriptionData.plan["id"]   Plan-id [订阅情况/Subscription]
            // 8 totalUsageCurr 未绑卡用户的本月用量
            // 以上 数据说明


            // 5-日期格式需要转换  5-到期时间/有效期/Expiration Date
            let date = new Date(data[5] * 1000);// data[5]为秒级时间戳，需要*1000转换为毫秒数
            let Y = date.getFullYear() + '-'; // 获取年份
            let M = String(date.getMonth() + 1).replace(/^(\d)$/, '0$1') + '-'; // 获取月份，注意月份是从 0 开始计数的，所以需要加 1
            let D = String(date.getDate()).replace(/^(\d)$/, '0$1') + ' '; // 获取日期
            let h = String(date.getHours()).replace(/^(\d)$/, '0$1') + ':'; // 获取小时
            let m = String(date.getMinutes()).replace(/^(\d)$/, '0$1') + ':'; // 获取分钟
            let s = String(date.getSeconds()).replace(/^(\d)$/, '0$1');  // 获取秒数
            data[5] = Y + M + D + h + m + s;  // Y+M+D+h+m+s 样式：2023-04-26 10:58:30

            // 定义金额向右对齐后的字符串长度  chatgpt推荐使用padStart函数，为了兼容更多浏览器，让gpt换了方法
            let alignLength = 10;

            //声明变量
            let msgTop, msg0, msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8, msgBottom;

            //绑卡用户与非绑卡用户的不同  绑卡和购买Plus应该是两回事，绑卡使用API 不一定有订阅Plus
            // * I don't have a payment account, so I can't verify the time range of data for payment users. However, hope the label is not wrong.
            if (data[1]) {
                //绑卡用户：后台绑定信用卡按量扣费的用户，不是每月固定扣费的Plus会员
                //  *根据网友的说法，绑卡后按月统计，根据API使用量扣费，理解如有误，请自行更改文本内容
                msg2 = window.currentlang == 'cn' ? '\r\n本月上限：  $' : '\r\nTotal Amount (M): $';  //TotalAmount per month
                msg3 = window.currentlang == 'cn' ? '\r\n实际消费：  $' : '\r\nTotal Usage (M): $'; //TotalUsage per month
                msg4 = window.currentlang == 'cn' ? '\r\n剩余限额：  $' : '\r\nRemaining (M): $'; //Remaining per month
                msg5 = window.currentlang == 'cn' ? '\r\n到期时间(?)：' : '\r\nExpiration Date(?): '; //ExpirationDate  绑卡用户是否有截止日期？ 月底？ per month??
                msg8 = window.currentlang == 'cn' ? '\r\n无' : '\r\nNone';  //null
            } else {
                //非绑卡用户：使用免费体验金的用户，历史统计数据
                msg2 = window.currentlang == 'cn' ? '\r\n账号总额：  $' : '\r\nTotal Amount: $';  //TotalAmount
                msg3 = window.currentlang == 'cn' ? '\r\n累计消耗：  $' : '\r\nTotal Usage: $'; //TotalUsage
                msg4 = window.currentlang == 'cn' ? '\r\n剩余额度：  $' : '\r\nRemaining: $'; //Remaining
                msg5 = window.currentlang == 'cn' ? '\r\n到期时间：  ' : '\r\nExpiration Date: ';  //ExpirationDate
                msg8 = (window.currentlang == 'cn' ? '\r\n本月消耗：  $' : '\r\nMonthly Usage: $') + " ".repeat(alignLength - data[8].toFixed(4).length) + data[8].toFixed(4); //MonthlyUsage 仅未绑卡用户 取4位小数点，并向右对齐
            }

            //组合需要展示的数据
            msgTop = window.currentlang == 'cn' ? 'OpenAI API 账号余额查询结果\r\n' : 'OpenAI API Balance Query Result\r\n';  // OpenAI API account balance query result
            //如果是apikey，而不是sessionKey
            if (apiKey_or_sessionKey.startsWith("sk-")) {
                //const msgTop_cn = '\r\n • 7月22日起，OpenAI 已停止通过 API Key 查询余额 (余额无数据)，现在查询余额需要使用 Session Key 。\r\n • 请通过点击“Session Key”按钮，输入Session Key 来查看余额信息。\r\n';
                //const msgTop_en = '\r\n • Starting from July 22nd, OpenAI has stopped displaying the balance when querying with an API Key. Now, to check the balance, you need to use a Session Key. \r\n • Please click the "Session Key" button and enter your Session Key to view the balance information.\r\n';
                const msgTop_cn = '\r\n • 自7月22日起，OpenAI 已停止使用 API Key 查询余额 (余额无数据)。现在需要使用 Session Key 来查询余额，但 Session Key 是动态变化的，目前暂时没有很好的余额查询方案。\r\n';
                const msgTop_en = '\r\n • Starting from July 22nd, OpenAI has stopped displaying the balance when querying with an API Key. Now, to check the balance, you need to use a Session Key. However, the Session Key is dynamically changing, so currently there is no good solution to query the balance.\r\n';
                msgTop = window.currentlang == 'cn' ? msgTop + msgTop_cn : msgTop + msgTop_en;
            }

            msg0 = (window.currentlang == 'cn' ? '\r\n账号归属：  ' : '\r\nAccount Name: ') + data[0]; // 注册OpenAI账号时填写的姓名  //AccountName
            msg1 = (window.currentlang == 'cn' ? '\r\n是否绑卡：  ' : '\r\nHas Payment: ') + (data[1] ? 'Yes' : 'No'); //has_payment  绑卡和购买Plus应该是两回事，绑卡使用API 不一定有订阅Plus
            msg2 = msg2 + " ".repeat(alignLength - data[2].toFixed(4).length) + data[2].toFixed(4); //取4位小数点，并向右对齐（计算需要补充的空格数量，在字符串前面添加空格）
            msg3 = msg3 + " ".repeat(alignLength - data[3].toFixed(4).length) + data[3].toFixed(4); //取4位小数点，并向右对齐
            msg4 = msg4 + " ".repeat(alignLength - data[4].toFixed(4).length) + data[4].toFixed(4); //取4位小数点，并向右对齐
            msg5 = msg5 + data[5];
            msg6 = (window.currentlang == 'cn' ? '\r\n账号用途：  ' : '\r\nAbout Plan(?): ') + data[6]; // Plan-title 应该是注册时选择的注册目的、用途
            const freetext = window.currentlang == 'cn' ? '免费计划' : 'Free Plan';
            msg7 = (window.currentlang == 'cn' ? '\r\n订阅情况：  ' : '\r\nSubscription: ') + (data[7] == 'free' ? freetext : (data[7] + ' (Paid Account ?)'));  //Plan-id  id等于free，则显示免费。 其他情况未知，显示原始值
            msg8 = msg8;

            const str1 = window.currentlang == 'cn' ? '\r\n*非绑卡用户默认统计范围为90天。' : '\r\n*Default query for 90 days usage.(For Non-Payment users)';
            const str2 = window.currentlang == 'cn' ? "\r\n*绑卡用户返回的数据应该都只是当月的，但我没绑过卡无法验证统计范围。" : "\r\n*I don't have a payment account, so I can't verify the time range of data for payment users. However, hope the label is not wrong.";
            const msg9 = data[1] ? str2 : str1;


            const msgBottom_cn = '\r\n\r\n余额查询代码 v7.23  \r\n以上信息均由官方接口返回，实时查询当前 API Key，不会存储数据，无泄漏风险，原理见代码。';
            const msgBottom_en = '\r\n\r\nBalance Inquiry Code Version 7.23  \r\nUse OpenAI API, real-time query, your data will not be saved, API Key can be changed at any time. ';
            msgBottom = window.currentlang == 'cn' ? msgBottom_cn : msgBottom_en;

            isCheckAPIKey = false;

            //查询结果 弹窗
            if (data[1]) {
                //绑卡用户的查询结果  后台绑定信用卡按量扣费的用户
                alert(msgTop + msg0 + msg1 + msg2 + msg3 + msg4 + msg5 + msg6 + msg7 + msgBottom + msg9);
            } else {
                //非绑卡用户的查询结果  比绑卡用户多一行数据：msg8
                alert(msgTop + msg0 + msg1 + msg2 + msg8 + msg3 + msg4 + msg5 + msg6 + msg7 + msgBottom + msg9);
            }

        }).catch((error) => {
            isCheckAPIKey = false;
            console.log(error);
            // Notification: Failed to check the balance. The reasons for the failure are not clear, and the possibility of "code malfunction" cannot be ruled out.(No impact on ChatGPT usage).
            const msg_cn = '!! 查询余额失败，存在多种可能，检查网络后重试(国内需要魔法)，仍失败的话，请放弃查余额～ \r\n\r\n不排除是查询余额代码失效的原因（不影响GPT的使用）。\r\n\r\n可查看代码是否有更新：\r\nhttps://gitee.com/GiteeKey/ruoyi-blog/ \r\nhttps://github.com/lin2025/gpt3.5/';
            const msg_en = "!! Failed to check the balance. The reasons for the failure are not clear, and the possibility of 'code malfunction' cannot be ruled out.(No impact on GPT usage) \r\n\r\nCheck for code updates:\r\nhttps://github.com/lin2025/gpt3.5/\r\n\r\nTip: If your location is not supported by OpenAI, use a VPN to check your balance.";
            const msg = window.currentlang == 'cn' ? msg_cn : msg_en;
            alert(msg);
        });
    }

    //以上为[查询OpenAI API余额]的代码  Javascript code to check OpenAI API balances
</script>

</body>
</html>
